تورهام 😳 | Django Tips – Telegram
تورهام 😳 | Django Tips
102 subscribers
4 photos
10 links
هیچی دیگه همچی داشت یادم میرفت گفتم اینجا بزارم قبل هر مصاحبه بخونم :)
Download Telegram
Transaction in #django

اگه نمیدونید به طور کلی ترنزاکشن چیه میتونید به این لینک سر بزنید. ولی خیلی خلاصه بخوام بگم به انجام چندتا کوئری مختلف ولی در یک unit ترنزاکشن میگن، یعنی شما ۳ تا کوئری انجام میدی، اگه حتی یکدونه از اون ۳ تا ارور بخوره بقیه ۲ تا هم هر تغییری داده باشن اون برمیگردونن یا به اصطلاح Rollback میکنه.

حالا #جنگو این قابلیت به شما میده که از این فیچر دیتابیس‌ها داخل کدتون استفاده کنید برای این کار میتونید از transaction.atomic استفاده کنید، این فانکشن هم قابلیت استفاده به عنوان دکوریتور رو داره هم کانتکس منیجر


به عنوان دکوریتور:
@transaction.atomic()
def something():
do_database_update()
do_database_delete()


یا به عنوان کانتکس منیجر:

def something():
with transaction.atomic():
do_database_update()
do_database_delete()

تو هر دو کد اگر اتفاقی داخل یکی از فانکشن ها بیوفته یعنی اگر ارور raise بشه کوئری‌ها و تغییراتی که رو دیتابیس اعمال شده همه rollback میشه و برمیگرده، شما میتونید چندتا ترنزاکشن به صورت nested هم انجام بدید برای مثال:

with transaction.atomic():
do_update():
with transaction.atomic():
do_delete()

تو اینجا باید یک نکته رو در نظر داشته باشید که جنگو هربار که یک nested میزنید از بیرون به داخل میره و هربار که وارد یک مرحله عمیق تر میشه یک savepoint میسازه از نستد بالایی یعنی اول ترنزاکشن آپدیت انجام میده بعد savepoint میسازه که بفهمه چیکار کرده بعد میره دومی، میتونید ساخت savepoint رو غیر فعال کنید با پاس دادن savepoint=false به فانکشن اتومیک که توصیه جنگو اینه که اینکار نکنید مگر اینکه واقعا مشکل پرفورمنس بخورید.

در پست بعدی درباره Transaction.on_commit صحبت میکنم :)

@TorhamDevCH
Transaction.on_commit in #django

قبل اینکه بریم سراغ موضوع اصلی بیایید چندتا نکته دیگه رو بفهمیم. داخل #جنگو شما لازم نیست تغییراتتون رو کامیت کنید مثلا اگر چیزی داخل دیتابیس آپدیت میکنید دیگه نمیایید خط بعد بنویسید Model.objects.commit)( به جاش از .save استفاده میکنید. ولی هرکسی که تاحالا با یک API لول پایین‌تر از ORM جنگو کار کرده میدونه که برای ثبت تغییرات باید commit بزنی. همینطوری که داخل بقیه کتابخونه و ... دیگه دیدی و میدونید شما میتونید آپشن آتوکامیت رو فعال کنید و دیگه لازم نباشه کامیت بزنید، داخل جنگو این آپشن به‌صورت پیش‌فرض فعال و میتونید داخل تنظیمات بخش دیتابیس اون غیر فعال کنید اما همه این‌ها رو گفتم که بگم جنگو زمانی که شما یک Transaction باز میکنید خودش این آتوکامیت رو در اون لحظه غیر فعال میکنه! دلیل منطقیش هم اینه که شما شاید ارور بخورید و بخوایید transaction رو rollback کنید در نتيجه اتوکامیت خاموش میشه. حالا چی میشه اگه شما بخوایید اگر یک transaction موفق بود(کامیت شد) بعدش یک کار رو انجام بدید؟ مثلا اگه موفق بود یک ایمیل بفرسته، خب آتوکامیت که خاموشه در نتیجه تو خطی که کد ران میشه کامیت اتفاق نمیوفته حالا جنگو اومده یک transaction.on_commit در اختیارتون گذاشته که یک callable میگیره و زمانی که اون transaction موفق بود و کامیتش کرد میاد این فانکشن شما رو اجرا میکنه.

* اگر هیچ ترنزاکشنی باز نکرده باشید و از فانکشن استفاده کنید فانشکن درجا اجرا میشه.*

نحوه استفاده:

def something():

with transaction.atomic():
transaction.on_commit(send_mail)


حالا قطعا اون وسط یک کوئری هم ران میکنید :)

@TorhamDevCH
مشکل concurrency چیه و چطوری میتونیم داخل #جنگو حلش کنیم؟

بیایید اول بفهمیم مشکل چی هست اصلا که میخواییم حلش کنیم. از زمانی که انسان‌ها موفق شدم چند پروسس رو همزمان اجرا کنن این مشکل به وجود اومد برای مثال این روزها دیگه شما فقط یک ترد پروژه جنگوتون اجرا نمیکنید بلکه با ابزارهای مثل گونیکورن و یونیکورن و ... چندتا ترد و پروسس ازش اجرا میکنید تا همچی سریعتر اتفاق بیوفته. اما این مشکل که اتفاق میوفته چیه؟

فرض کنید شما یک سیستم بانکی دارید و داخل این بانک یک حساب مشترک دارید بین user1 و user2. هردو این یوزرها به این حساب دسترسی دارن یوزر اول پرداخت کننده است و یوزر دوم برداشت کننده حالا فکر کنید بالانس(موجودی) حساب ۱۰۰۰ دلاره.

در همین لحظه که ما هستیم user1 میخواد ۱۰۰ دلار به حساب واریز کنه و دقیقا همزمان باهاش user2 میخواد ۱۰۰ دلار برداشت کنه. سوال اینه که در آخر این برداشت و واریز موجود یا همون بالانس حساب چقدر خواهد بود؟ جواب منطقی ما اینه که بالانس همون هزار دلار خواهد بود چون ۱۰۰ دلار اومد و ۱۰۰ دلار هم رفت و آره! اگه برنامه ما فقط و فقط یک پروسس باشه این ۲ درخواست به ترتیب اجرا خواهد شد و بالانس همون هزار دلار میشه اما اگه برنامه بیشتر از یک پروسس باشه چه اتفاقی میوفته؟

خوب بیایید فرض کنیم این بار درخواست اول به پروسس شماره ۱ و درخواست دوم به پروسس شماره ۲ میره و این دو همزمان از دیتابیس بالانس حساب میخونن تا درنهایت بهش جمع و منها بزنن دیگه. مراحل این خواهد شد.

۱. یوزر اول درخواست میزنه و موجودی ۱۰۰۰ دلار رو دریافت میکنه
۲. یورر اول موجودی آپدیت میکنه به ۱۱۰۰ دلار .

۳. یوزر دوم دقیقا تو همون لحظه درخواست میزنه و قبل اپدیت شدن موجودی اون میگیره و نتیجه موجودی برای ۱۰۰۰ دلاره
۴. ازش ۱۰۰ تا کم میکنه و ۹۰۰ رو به عنوان بالانس ذخیره میکنه.


و درنهایت موجودی شد ۹۰۰ دلار! این ماجرا میتونست برعکس هم اتفاق بیوفته و موجودی بشه ۱۱۰۰ دلار که همش نتیجه یک چند صدم ثانیه اختلاف بین ریکوئست یک و دو بود. نکته اصلی این بود که اینا قبل اینکه اون یکی موجودی آپدیت موجودی رو میخوندن و داخل مموری ذخیره میکردند در نتیجه تو اپدیت هم اشتباه اپدیت میکردند.


حالا که فهمیدید ماجرا از چه قرار شما میتونید داخل #django با استفاده از select_for_update و ترنزاکشن که پست‌ها قبل توضیح دادم این ماجرا حل کنید. ( در حقیقت جنگو ماجرا رو حل نمیکنه بلکه با استفاده از متدها شما به جنگو میگید کوئری بسازه که نتیجه اش این بشه که دیتابیس براتون یک لاک رو row که میخوایید آپدیت کنید بگیره)


حالا روش استفاده و توضیحات بیشتر میتونید تو مقاله پایین بخونید. این پست خلاصه‌ای کوتاه از مقاله زیر بود ( خود مقاله هم کوتاه)

https://www.sankalpjonna.com/learn-django/managing-concurrency-in-django-using-select-for-update

@TorhamDevCH
یک نکته جالب:

تو عکس یک تودو میبینید که نوشته منطق withdraw یا همون برداشت رو اینجا توسعه بده.

نکته ای که میخوام بگم اینه که ما باید سعی کنیم بیزینس لاجیک رو تو یکجا نگه داریم تا جای ممکن حالا این تصمیم شماست که کجا قرارش بدید. توصیه جنگو اینه که سعی کنید اینجور چیزا نزدیک به مدل نگه دارید چون در نهایت بیشتر لاجیک‌ها به crud ختم میشه در نتیجه شاید بهتر باشه از این ویو صرفا جهت routing استفاده کنیم و داخلش یک فانکشن ران کنیم که برداشت برامون انجام بده مثلا همچین چیزی:


Wallet.objects.get(pk=id).do_withdraw(amount=100)



خب این یعنی من یک فانکشن رو مدل Wallet دارم که این کار قرار برام انجام بده.

یا به عبارتی:



@transaction.atomic()
def deposit(self, *, amount: int):
obj = self.get_queryset().select_for_update().get()
obj.balance = models.F("balance") + amount
obj.save()

Transaction.objects.create(
amount=amount,
wallet=self,
tr_type=TransactionsType.DIPOSIT,
status=TransactionsStatus.SUCCESS,
)

@transaction.atomic()
def withdraw(self, *, amount: int): ...



اینطوری اگه کسی متدهای مدل بخونه تا حد زیادی لاجیک‌‌هارو میفهمه و اینکه میدونه اگه دنبال یک لاجیک میگرده کجا بره!


@TorhamDevCH
👍1
get_object_404 in #django

فریم‌ورک #جنگو یکسری shortcuts یا همون میان‌بر داره که کد زدن شما رو کمتر میکنه و خیلی کاربردی ان. میتونید تمام اونها از این مسیر ایمپورت کنید:

from django.shortcuts import <NAME>


امروز میخوام درباره یکی از میان‌بر ها بگم به اسم get_object_404 این شرت‌کات کار نوشتن کد شما رو کمتر میکنه. بیشتر مواقع وقتی شما میخوایید یک آبجکت رو در دیتابیس کوئری بزنید باید این کار رو کنید: اگر آبجکت بود اون رو برگردون اگر نبود ارور ۴۰۴ بده.

حالا شما باید این کار رو هرجا که میخوایید یک چیزی از دیتابیس بگیرید انجام بدید. خلاصه هربار باید همچین کدی بزنید:


def get_object(self):
try:
return Record.objects.get(id=self.request.query_params['id'])
except Record.DoesNotExist:
raise Http404()



حالا جنگو اومده این کار براتون کرده یک فانکشن. فقط صداش میزنید و مدل و کوئری که میخوایید بزنید رو بهش میدید و اگر وجود داشت آبجکت بهتون میده اگه نداشت خودش ارور ۴۰۴ میده.


def get_object(self):
return get_object_or_404(Record, id=self.request.query_params['id'])


این شرت‌کات یک نمونه مشابه داره به اسم get_list_or_404 که این یکی به شما لیستی از ابجکت‌ها رو برمیگردونه. اولی فقط و فقط یک آبجکت برمیگردوند.

@TorhamDevCH
👍1
بیایید به بهانه آپتومایز کردن کوئری‌های #جنگو چندتا چیز جدید درباره ORM جنگو یاد بگیریم

شما همیشه وقتی چیزی رو نیاز دارید داخل جنگو همچین کوئری میزنید:

Record.objects.filter(id__in=[1,2,3,4])


خب این قرار آبجکت‌هایی که ایدی ۱ تا ۴ دارن بهمون بده. حالا شما میخوایید با اینا یکسری پردازش انجام بدید مثلا بیایید اسم همه رکوردها رو نشون کار برید یا به عبارتی:


for r in records:
print(record.name)


و کار شما اینجا تموم میشه و خوشحال میشید. اماااااااا شما یک عالمه پردازش بی جا انجام دادید و یک عالمه منابع الکی خرج کرید. بیایید ببینیم کوئری بالا وقتی sql میشه چه شکلی میشه:



SELECT id,
name,
created_at,
is_deleted
FROM records
WHERE id IN (1, 2, 3, 4);


این چیزی که داخل دیتابیس اجرا میشه. متوجه اش شدید؟

اگه نشدید باید بگم شما فقط به فیلد name نیاز داشتید اما تمام فیلدهای اون اون رکوردها رو گرفتید! هیچ وقت هم ازشون استفاده نکردید.

برای حل این مشکل جنگو دوتا راه حل داره:
1. values
2. values_list

با استفاده از این دو میتونید فقط فیلدهایی که میخوایید رو بگیرید. برای مثال:



Record.objects.filter(id__in=[1,2,3,4]).values('name')



و حالا کوئری که میسازید همچین چیزی خواهد شد:


SELECT name
FROM records
WHERE id IN (1, 2, 3, 4);



و همینطور که میبینید حالا فقط اون فیلدی رو گرفتید که لازمش دارید.

تفاوت بین values و valuse_list تنها در دیتا استراکچر خروجی که به شما میده و داخل کوئری نهایی هردو مثل هم عمل میکنن. برای درک بیشتر:



>>> Record.objects.filter(is_deleted=False).values('id', 'name')
<QuerySet [{'id': 1, 'name': 'First record'}, {'id': 2, 'name': 'Second Record'}, {'id': 3, 'name': 'Third Record'}]>

>>> Record.objects.filter(is_deleted=False).values_list('id', 'name')
<QuerySet [(1, 'First record'), (2, 'Second Record'), (3, 'Third Record')]>


بله یکی دیکشنری و دومی تاپل :) همچنین اگه فقط فقط یک فیلد میخوایید مثلا name میتونید از flat=True هم استفاده کنید برای بهتر شدن دیتا خروجی:


>>> Record.objects.filter(is_deleted=False).values_list('name',flat=True)
<QuerySet ['First record', 'Second Record', 'Third Record']>


@TorhamDevCH
👍1
چطور کوئری آپدیت بهتری داخل #جنگو بزنیم؟

روش‌های زیادی برای آپدیت کردن یک آجکت یا چندتا آبجکت داخل جنگو وجود داره، ساده ترین حالتی که افراد استفاده میکنن همچین چیزی.

مدل فرضی:

class Records(models.Model):
name = models.Charfield()
balance = models.InetegerField()
country =models.CharField()

برای مثال اگه کسی بخواد یک آبجکت از این مدل رو آپدیت کنه همچین کار میکنه:

r = Records.objects.get(pk=1)
r.name = "new name"
r.save()

که این اوکیه، بد نیست و آپدیت براتون انجام میده اما یک نکته رو بهش توجه نمیکنید!

زمانی که شما یک آبجکت این شکلی آپدیت میکنید در اصل دارید تمام فیلد‌ها رو آپدیت میکنید :) ولی خب مقدار فیلدهای قبلی همون قبلی ها آپدیت میشه، برای اینکه از این کار جلو گیری کنید باید explicit ( نمیدونم، فکر کنم دقیق تر معنی بده) باشید یعنی، دقیقا بگید کدوم فیلد/فیلدها میخوایید آپدیت کنید. این کار میکنید با استفاده از پارامتر update_fields انجام بدید.

r = Records.objects.get(pk=1)
r.name = "new name"
r.save(update_fields=["name"])


البته باید مراقب باشید که حتما فیلدهایی که میخوایید آپدیت کنید رو داخلش بزارید مگرنه آپدیت نمیشن.

پست بعدی آپدیت کردن چند آبجکت...


@TorhamDevCH
👍1
آپدیت کردن چند آبجکت به صورت همزمان در #جنگو

فریم‌ورک #django قابلیت آپدیت کردن دیتاها رو به روش ها مختلف داره که خیلی ها یا ازش بی خبر ان یا استفاده نمیکنن. بیایید ببینیم هر کدوم رو کجا استفاده کنی بهتره :)

مدل فرضی:

class Records(models.Model):
name = models.Charfield()
balance = models.InetegerField()
country =models.CharField()

خب فرض کنید ما یک هدیه به مناسب عید نو روز میخواییم به کاربرا بدیم، مثلا میخاییم نفری ۲ هزار تومن هدیه بدیم D:

حالا چند روش وجود داره.

روش اول ( نوب):
users = Records.objects.all()

for user in users:
user.balanc = user.balance + 2
user.save()

خیلی ساده و البته درب و داغون در خیلی جهات. مشکل اول اینه که ما رو همه کاربرا حلقه میزنیم و هر بار آپدیت رو روی کاربرا صدا میزنیم یعنی برای هر یوزر یک درخواست اپدیت به دیتابیس میره که اگه ۱ میلیون یوزر داشته باشیم ۱ میلیون درخواست میره :).

( تو اینه پست به اینکه باید از F استفاده کنید یا کانکارنسی و اینا هندل کنید اشاره نمیکنم، پست‌ها قبلی بخونید)

حالا روش بهتر چیه؟

روش بهتر:

user = Records.objects.update(balance=F("balance") + 2 )

همینقدر ساده :)


سناریو دوم: با بک‌اند یک بازی خفن رو داریم توسعه میدیم، داخل این بازی هر هفته یک ایونت اتفاق میوفته که افرادی که اون رو تموم کنن در آخر هفته یک تایتل به کنار اسمشون اضافه میشه و همچنین اگه امتیاز بالاتر از ۱۰ کسب کرده باشن به بالانس پول داخل گیم‌شون هم ۱۰۰ تا گلد اضافه میشه.


حالا بیایید فقط کوئری آپدیت این بهش ببینیم، فرض کنید این کوئری آخر هفته اجرا میشه. ( این فیلدا تو مدل فرض نداریم دیگه خودتون فرض کنید هست 😂❤️)

users = Records objects.fileter(done_weekly=True)

for user in users:
user.name = "Grunt " + user.name
if user.weekly_score >= 10:
user.balance = user.balance + 100

user.save()


خب همینطور که خیلی معلومه مشکلات فراوان داخلش هست. بزرگ‌ترین مشکلش اینه که هر بار برای هر کاربر یک درخواست آپدیت میدیم که میشهه همون مشکل بالا، آما آیا این بار میشه از روش بالا استفاده کرد و اینو فیکسش کرد؟ نه
روش بالا زمانی کاربرد داره که فیلدها قراره یک مقداری ثابتی به همشون داده بشه، اینجا بعضی ها ۱۰۰ تا گلد میگیرن بعضی ها نه پس کار نمیکنه، اینجا ما میتونیم از فانکشن bulk_update جنگو استفاده کنیم.

همون حلقه بالا رو میزنید با این تفاوت که داخلش .save رو صدا نمیزنید و تمام آبجکت‌ها رو داخل مموری آپدیت میکنید و بعد همچین حرکتی میزنید:

Records.objects.bulk_update(updated_users_list)

و تموم همرو با هم آپدیت میکنید با یک درخواست اینجا حتی میتونید یک قدم جلوتر برید و با اضافه کردن updated_fields به ورودی فانشکن و مشخص کردن اینکه دقیقا دوتا فیلد بالانس و نام فقط قرار آپدیت بشه بهترش کنید!

از این به بعد بهتر آپدیت کنید :)

@TorhamDevCH
👍1
سر این پروژه یکچیزی هم درباره جنگو یاد گرفتم.
مسئله: نیاز داریم یک فیلد به مدل دیفالت جنگو اضافه کنیم چون اورایت کردنش nmt حال ندارم.

جواب:

خب چندتا حالت میشه اینکار کرد، اول اینکه یک مدل جدا بزنید فارنکی با one2one فیلد کنید به مدل اصلی جنگو یا اینکه AbestractUser رو از خود جنگو ایمپورت کنید باهاش یک مدل جدید بسازید و فیلدا و هرچی میخوایید بهش اضافه کنید بعدش داخل تنظیمات بگید مدل ادمین جدید اینه.

من حرکت دوم زدم چون به نظرم اوکی بود.

نکته: روش‌های بالا همشون یکچیزی به مدل یوزر خود جنگو اضافه میکنن، نمیتونید ایطوری فیلدی ازش کم کنید(البته شاید بشه فیلدا اورایت کرد نمیدانم تست نکردم) و اینکه با این روش‌ها نمیتونید فانکشنالیتی مدل تغییر بدید تهش فانکشنالیتی جدید اضافه کنید مثلا نمیتونید set_password تغییر بدید. برای اون کار باید بزنید از base خودتون یک مدل یوزر بسازید به جنگو معرفی کنید :) فقط یک سری فیلد مشخص مثل is_staf داخلش باید باشه تا جنگو بتونه باهاش کنار بیاد.

آره خلاصه :)

@TorhamDevCH
👍1
TorhamDev | تورهام 😳
رفرنس داکیومنت به این ماجرا داکیومنتش بخونید دانا و نابغه بشید:)
جالبه همین خط اول عکس اول بخونید، توصیه جنگو اینه که هر پروژه‌ای میزنید حتی اگه مدل دیفالت نیاز‌هاتون برطرف میکنه بهتر مدل خودتون بسازید و معرفی کنید‌.
👍1
Django QuerySet - only() vs defer() vs exclude()

If you have some fields in your Django model that contain a lot of data and you don't need those fields for a particular query, you can tell Django not to retrieve them with defer():
Event.objects.defer("denoscription")
While defer() works at the attribute level, exclude() works on the row level.
In other words, exclude() arguments are used after the WHERE clause -- i.e., SELECT * FROM users WHERE name = 'jan' -- while defer() changes * to the provided fields -- i.e., SELECT name, email FROM users.
Opposite to defer() is only(). If you have fewer fields that you want to retrieve than those you don't, you can use only() to retrieve only the fields provided as arguments:

Event.objects.only("noscript")
👍2
QuerySet.contains(obj)
👍2
نکته رندوم درباره orm #جنگو

@TorhamDevCH
👍1
TorhamDev | تورهام 😳
نکته رندوم درباره orm #جنگو @TorhamDevCH
ماجرا اینه که زمانی که شما objects.get میزنید درحقیقت دارید همچین کوئری میسازید:

SELECT * FROM MyModel WHERE id=1


و بعد وقتی پشت سرش .delete() میزنید اتفاقی که میوفته اول کوئری select رو میزنه دیتا رو میگیره و بعد دوباره کوئری delete رو میزنه.


تو حالت دوم که از filter استفاده میکنید دیگه یک راست میاد کوئری دلیت رو میزنه و دیگه گت نمیکنه در نتیجه اگر دیتا وجود هم نداشته باشه اروری نمیخورید چون خوب اصلا نگرفتیدش که ارور بخورید. در نهایت اگه تمام کارتون از یک دیتا اینه که حذفش کنید با filter.delete بزنید خیلی بهتره.


@TorhamDevCH
👍5
امروز یکسری حالات مختلف از teardown کردن تست‌ها در pytest یاد گرفتم خوب بود هر کدوم کاربرد و جای خودش داره.

به سه حالت مختلف رسیدم. حالت اول زمانی که شما نیاز به یک resource دارید برای تست‌هاتون برای مثال اول لاگین کنید و بعدش ریکوئست بزنید. تو این مواقع بهتر که یک fixture داشته باشید که براتون لاگین کنه و وقتی کارتون تموم شد خودش لاگ اوت کنه.

به اون پروسه لاگ‌اوت کردن میگن teardown یکدونه هم tear up داریم که پروسه لاگین کردن تو این مثاله. مقال دیگش میشه زمانی که نیاز دارید یک رکورد خاص داخل دیتابیس ساخته بشه و بعد از تست حذف بشه. به ساختنش میگن tear up به حذف کردنش میگن tear down.

خب حالا حال اول که fixture باشه.

import pytest

@pytest.fixture
def client() -> AuthedClient:
#login and etc
yeild client
client.logout()

داخل فیکسچر‌ها pytest هرچیزی که بعد از yield بیارید teardown و هرچی که قبلش بیاد tearup.


حالت دوم شما یک مقدار از درون تست نیاز دارید برای tear down کردن. برای مثال شما یک تست دارید پست زدن داخل توییتر رو تست میکنه. شما برای teardown کردن این تست لازم دارید پست رو پاک کنید اما برای پاک کردنش نیاز به id اون پست دارید. اینجاس که شما یک مقدار لازم دارید که داخل خود تست‌ ساخته شده.
برای این مورد به نظر من بهترین حالت در حال حاظر با دانش الان من استفاده از try-finally هستش.

def test_twt_post_create_success(twt_client):
post_id = None
try:
post = twt_client.post("Hello from test")
post_id = post.id
finally:
if post_id:
twt_client.remove_post(post_id)

اینجا فیکسچر‌های شما که برای مثال twt_client هست براتون کلاین tear up و tear down میکنن به روش اول. و try-finally پستی که ساختید رو tear down میکنه. مهدی سینیور ما باشد گفت که یک فیکسچر بسازم که داخلش یک yeild خالی باشه و بعد از yeild از داخل یک متغیر گلوبال بیاد ایدی پست ها رو بخونه و همرو حذف کنه که برای این کار لازمه داخل هر تست هر وقت پست ساختیم اضافه اش کنید به اون متغیر که من به نظرم try finally بهتر بود در نتیجه همون زدم فرستادم تک‌لید :)

@TorhamDevCH
👍1
یک چیز جالب دیروز درباره pytest خوندم داخل داکیومنتش و اینه که شما میتونید scope یک fixture رو داینامیک کنید نسبت به تستی که اجرا میکنید.

def determine_scooe(fixture_name, config):

if config.getoption("--keep-containers", None)
return "session"
return "function"


@pytest.fixture(scope=datermine_scope)
def docker_container():
yield spwan_container()

این مثال خود داکیومنت و همینطوری که میبینید اگه یک فانکشن(callable) به جای اسکوپ بدید پای‌تست اون اجرا میکنه و دوتا ورودی بهش میده و از خروجی اون برای scope استفاده میکنه.

@TorhamDevCH
یک چیز دیگه هم چند روزه میخواستم بنویسم دربارش. درباره functions.wraps

زمانی که یک دکوریتور مینویسید اگر از wraps استفاده نکنید باعث میشید سیگنچر فانکشن‌هایی که از دکوریتور استفاده میکنن تغییر کنه.

def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " called")
return func(*args, **kwargs)

return with_logging
`

وقتی شما از این دکوریتور استفاده میکنید برای مثال:
@logged
def foo(x):
return x ** x

در حقیقت دارید میگید
def foo(x):
return x ** x

foo = logged(foo)


حالا اتفاقی که میوفته اینه که سیگنچر foo تغییر میکنه به logged یعنی اگر شما داک استرینگ foo بگیرید بعد دکوریت شدن توسط logged چیزی که خواهید دید داک استرینگ logged. میتونید داک استرینگ رو با داندرلاین doc بگیرید.

حالا اگر از @wraps استفاده کنید این اتفاق نمیوفته و سیگنچر فانکشن foo باقی خواهد موند.

from functools import wraps

def logged(func):
@wraps
def with_logging(*args, **kwargs):
print("logged")
return func(*args, **kwargs)

@logged
def foo(x):
return x * x

اره خلاصه

@TorhamDevCH
1