IO: In-memory filebuffer

যারা মোটামুটি ভালোমানের টেক্সট এডিটর(Vim, Emacs, Atom, Sublime etc.) ব্যবহার করে আসছেন তারা ফাইল(file) ও বাফার(buffer) এর মধ্যে পার্থক্য জানেন। বাফার হচ্ছে সহজ কথায় মেমরিতে লোড করা ফাইল। পাইথনে এমন বাফার হিসেবে ব্যবহার করা যায় বিল্ট-ইন io লাইব্রেরির BytesIO, StringIO এবং RawIO মেথডগুলি। এখন প্রশ্ন হচ্ছে কখন ব্যবহার করবো? তখনই ব্যবহার করবো যখন আমার একখণ্ড ডাটা প্রয়োজন যা ফাইলের মত আচরণ করবে। অর্থাৎ একটা file object চাই ফাইল ছাড়াই। মনে করুন আমরা একটা QRCode Generator বানাচ্ছি। আসলে বেশ কয়েকটা QRCode generator library আছে ইতমধ্যে। আমাদের যা প্রয়োজন তা হচ্ছে আমরা QRCode-টিকে একটা 1.91:1 অনুপাতের ব্যাকগ্রাউন্ডের মাঝখানে রাখবো। সাধারনভাবে আমরা যা করতে পারি তা হচ্ছে একটা QRCode ছবি তৈরী করবো, তারপর PIL লাইব্রেরি দিয়ে একটি ব্যাকগ্রাউন্ড তৈরী করে তার ওপর আগের বানানো ছবি থেকে রিড করে বসিয়ে দেবো। এক্ষেত্রে আসলে দুটো ফাইল তৈরী হবে যার একটি অপ্রয়োজনীয়।

And then… IO steps in to save the day… :smile: আমাদের দুটো লাইব্রেরি লাগবে, ইন্সটলড্ না থাকলে pip দিয়ে ইন্সটল করে নেন। লাইব্রেরিগুলো হচ্ছে qrcodePillow

তো প্রথমে আমরা একটা QRCode তৈরী করবো:

import qrcode

data = "https://uroybd.github.io"
# QRCode definition
qcode = qrcode.QRCode(version=1,
                      error_correction=qrcode.constants.ERROR_CORRECT_L,
                      box_size=8,
                      border=0)
qcode.add_data(data)
qcode.make(fit=True)
# Image Generate করলাম
qr_image = qcode.make_image()

আমরা এটিকে এখন একটা ফাইলে সেভ করতে পারি(কিন্তু করবো না) এভাবে:

with open("qcode.png", "w") as dfile:
    qr_image.save(dfile)

তার বদলে আমরা একটি বাফার তৈরী করে তাতে ডাটা রাখবো যেন এটিকে ফাইলের মতই ব্যবহার করা যায়। আমরা BytesIO ব্যবহার করছি কেননা এটি বাইনারি ডাটা:

from io import BytesIO
# একটা বাফার তৈরী করি QRCode এর ডাটা রাখতে
buffer = BytesIO()
qr_image.save(buffer)  # বাফারে সেভ করি

এখন এই বাফারটিকে আমরা ফাইলের মতই ব্যবহার করতে পারবো। ব্যাকগ্রাউন্ড যোগ করা যাক:

from PIL import Image

image = Image.open(buffer, 'r')
image_width, image_height = image.size
background = Image.new('RGBA', (191*2, 100*2), (255, 255, 255, 255))
bg_width, bg_height = background.size
offset = (int((bg_width - image_width) / 2), int((bg_height - image_height) / 2))
background.paste(image, offset)
background.save(filename, format="png")  # ফাইলে সেভ করি

এখন আমরা এটাকে যদি একটি ফাংশনে রূপ দিই তবে সব মিলে দাঁড়াচ্ছে:

def gen_custom_qrcode(data, filename):
    # QRCode definition
    qcode = qrcode.QRCode(version=1,
                          error_correction=qrcode.constants.ERROR_CORRECT_L,
                          box_size=8,
                          border=0)
    qcode.add_data(data)
    qcode.make(fit=True)

    qr_image = qcode.make_image()
    buffer = BytesIO()
    qr_image.save(buffer)
    # বাফারটিকে ফাইল হিসেবে খুলি
    image = Image.open(buffer, 'r')
    image_width, image_height = image.size
    background = Image.new('RGBA', (191*2, 100*2), (255, 255, 255, 255))
    bg_width, bg_height = background.size
    # QRCode মাঝখানে বসানোর জন্য অফসেটের মান
    offset = (int((bg_width - image_width) / 2),
              int((bg_height - image_height) / 2))
    background.paste(image, offset)
    background.save(filename, format="png")

এখন আমরা gen_custom_qrcode ফাংশনটি ব্যবহার করে ডাটা ও আউটপুট ফাইলনেম আর্গুমেন্ট হিসেবে ব্যবহার করে আমাদের কাস্টম QRCode generate করতে পারবো। মধ্যবর্তী কোনো ফাইল ছাড়াই।

বাস্তবজগতে একটা ব্যবহার বলি। আপনি প্রোগামিটিক্যালি QRCode বা যেকোনো ফাইল জেনারেট করে বাফারে সেভ করে সেটা থেকে InMemoryUploadedFile দিয়ে জ্যাঙ্গোর মডেলে সরাসরি আপলোড দিতে পারেন, ব্যবহার করতে পারেন প্রয়োজনমত। মাঝখানে একটা ফাইল জেনারেট করা, সেটা আপলোড ও পুরোনো ফাইল ডিলিটের ঝামেলায় পড়তে হবে না।