Ajitabh Pandey's Soul & Syntax

Exploring systems, souls, and stories – one post at a time

Tag: python

  • Simplify Your Python Project Configurations


    Have you ever started a new Python project and, within a week, everything already feels messy?

    Your config.py file is slowly becoming a dumping ground. There are commented lines everywhere, database URLs hardcoded directly in the file, and if ENV == “prod” conditions scattered across the codebase. At first, it feels manageable. But very quickly, it becomes difficult to understand what is actually being used and what is not.

    And somewhere in the back of your mind, there is always that small fear: What if I accidentally expose a production password or push the wrong configuration?

    This kind of setup might work for a small script. But as the project grows, it becomes hard to maintain and almost impossible to scale properly. And yes, this still happens even in the modern world of AI-assisted coding, irrespective of which model we use.

    Over time, I realized that the cleanest way to handle configuration is not through complex .ini files or deeply nested dictionaries. I prefer using Python class inheritance along with environment variables. In some projects, I also pair this with Pydantic for validation when things get more complex.

    Here’s how I structure my configuration systems to keep them type-safe, secure, and, most importantly, easy to read.

    The Foundation

    First, we need to talk about secrets. Hardcoding a Telegram token inside your code is basically inviting trouble. The simplest solution is to move sensitive values into a .env file and load them from environment variables.

    One important rule. Never commit your .env file to Git. Instead, keep a .env.example file with empty placeholders so your team knows what variables are required.

    Example .env file for local development:

    # .env file (Local only!)

    TG_LIVE_TOKEN=55667788:AABBCC_Example
    TG_LIVE_CHAT_ID=-100123456789
    DATABASE_URL=sqlite:///app.db

    Now, instead of scattering values everywhere, I keep a single configuration file which acts as the source of truth.

    import os

    class Config:
    """Common settings for all environments"""

    SECRET_KEY = os.environ.get("SECRET_KEY", "change-this-in-production")
    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db")
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    TELEGRAM_BOTS = {
    "Live Notifications": {
    "bot_token": os.environ.get("BOT_TOKEN_LIVE"),
    "chat_id": "-1234455"
    },
    "Admin Bot": {
    "bot_token": os.environ.get("BOT_TOKEN_ADMIN"),
    "chat_id": "-45678"
    }
    }

    This class holds the defaults. Everything common lives here. No duplication.

    When I need environment-specific behavior, I simply inherit and override only what is required.

    For example, in end-to-end testing, I might want notifications enabled but routed differently.

    class E2EConfig(Config):
    """Overrides for E2E testing"""
    TESTING = True
    TELEGRAM_SEND_NOTIFICATIONS = True
    E2E_NOTIFICATION_BOT = 'Admin Bot'

    For unit or integration testing, I definitely do not want real Telegram messages going out. I also prefer an in-memory database for speed.

    class TestConfig(Config):
    """Overrides for local unit tests"""
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # Use in-memory DB for speed
    TELEGRAM_SEND_NOTIFICATIONS = False
    WTF_CSRF_ENABLED = False

    Notice something important here. I am not copying the entire base class. I am only overriding what changes. That alone reduces many future mistakes.

    To avoid magic strings floating around in the logic layer, I sometimes pair this with enums.

    from enum import Enum

    class LogType(Enum):
    STREAM_PUBLISH = 'STREAM_PUBLISH'
    NOTIFICATION = 'NOTIFICATION'

    Now my IDE knows the valid options. Refactoring becomes safer. Typos become less likely.

    Loading the configuration is also simple. In Flask, I usually use a factory pattern and switch based on one environment variable.

    import os
    from flask import Flask
    from config import Config, E2EConfig, TestConfig

    def create_app():
    app = Flask(__name__)

    # Select config based on APP_ENV environment variable
    env = os.environ.get("APP_ENV", "production").lower()

    configs = {
    "production": Config,
    "e2e": E2EConfig,
    "test": TestConfig
    }

    # Load the selected class
    app.config.from_object(configs.get(env, Config))

    return app

    That is it. One variable controls everything. No scattered if-else checks across the codebase.

    Over time, this pattern has saved me from configuration-related surprises. All settings live in one place. Inheritance avoids copy-paste errors. Tests do not accidentally spam users because TELEGRAM_SEND_NOTIFICATIONS is explicitly set to False in TestConfig.

    And if tomorrow I need a StagingConfig or DevConfig, I just add a small class that extends Config. Three or four lines, and I am done.

    Configuration management may not be glamorous, but it decides how stable your application feels in the long run. A clean structure here reduces mental load everywhere else.

  • Using Telegram for Automation Using Python Telethon Module

    Using Telegram for Automation Using Python Telethon Module

    Telegram is a cloud based messaging application which provides an excellent set of APIs to allow developers to automate on top of the platform. It is increasingly being used to automate various notifications and messages. It has become a platform of choice to create bots which interact with users and groups.

    Telethon is an asyncio Python 3 library for interacting with telegram API. It is one of the very exhaustive libraries which allows users to interact with telegram API as a user or as a bot.

    Recently I have written some AWS Lambda functions to automate certain personal notifications. I could have run the code as a container on one of my VPSs or on Hulu or other platforms, but I took this exercise as an opportunity to learn more about serverless and functions. Also, my kind of load is something which can easyly fall under the Lambda free tier.

    In this post we will look into the process of how to start with the development and write some basic python applications.

    Registering As a Telegram Developer

    Following steps can be followed to obtain the API ID for telegram –

    • Sign up for Telegram using any application
    • Login to the https://my.telegram.org/ website using the same mobile number. Telegram will send you a confirmation code on Telegram application. After entering the confirmation code, you will be seeing the following screen –
    Screenshot of Telegram Core Developer Page
    • In the above screen select the API Development Tools and complete the form. This page will provide some basic information in addition to api_id and api_hash.

    Setting up Telethon Development Environment

    I assume that the reader is familiar with basic python and knows how to set up a virtual environment, so rather than explaining, I would more focus on quick code to get the development environment up and running.

    $ mkdir telethon-dev && cd telethon-dev 
    $ python3 -m venv venv-telethon
    $ source venv-telethon/bin/activate
    (venv-telethon) $ pip install --upgrade pip
    (venv-telethon) $ pip install telethon
    (venv-telethon) $ pip install python-dotenv

    Obtaining The Telegram Session

    I will be using .env file for storing the api_id and api_hash so that the same can be used in the code which we will write. Replace NNNNN with your api_id and XX with your api_hash

    TELEGRAM_API_ID=NNNNN
    TELEGRAM_API_HASH=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Next we will need to create a session to be used in our code. For full automation, it is needed that we store the session either as a file or as a string. Since the cloud environments destroy the ephimeral storage they provide, so I will get the session as a string. The following python code will help obtain the same.

    #! /usr/bin/env python3
    
    import os
    
    from dotenv import load_dotenv
    
    from telethon.sync import TelegramClient
    from telethon.sessions import StringSession
    
    load_dotenv()
    
    with TelegramClient(StringSession(), os.getenv("TELEGRAM_API_ID"), os.getenv("TELEGRAM_API_HASH")) as client:
        print(client.session.save())

    When this code is executed, it will prompt for your phone number. Here you would need to print the phone number with the country code. In the next step, an authorization code will be received in the telegram application which would need to be entered in the application prompt. Once the authorization code is typed correctly, the session will be printed as a string value on standard output. You would need to save the same.

    (venv-telethon) $ ./get_string_session.py
     Please enter your phone (or bot token): +91xxxxxxxxxx
     Please enter the code you received: zzzzz
    Signed in successfully as KKKKKK KKKKKKK
    9vznqQDuX2q34Fyir634qgDysl4gZ4Fhu82eZ9yHs35rKyXf9vznqQDuX2q34Fyir634qgDyslLov-S0t7KpTK6q6EdEnla7cqGD26N5uHg9rFtg83J8t2l5TlStCsuhWjdzbb29MFFSU5-l4gZ4Fhu9vznqQDuX2q34Fyir634qgDysl9vznqQDuX2q34Fyir634qgDy_x7Sr9lFgZsH99aOD35nSqw3RzBmm51EUIeKhG4hNeHuF1nwzttuBGQqqqfao8sTB5_purgT-hAd2prYJDBcavzH8igqk5KDCTsZVLVFIV32a9Odfvzg2MlnGRud64-S0t7KpTK6q6EdEnla7cqGD26N5uHg9rFtg83J8t2l5TlStCsuhWjdzbb29MFFSU5=

    I normally put the string session along with the API ID and Hash in the .env file. All these three values would need to be protected and should never be shared with a third party.

    For the next code, I will assume that you have used a variable TELEGRAM_STRING_SESSION. So the final .env file will look like below –

    TELEGRAM_API_ID=NNNNN
    TELEGRAM_API_HASH=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    TELEGRAM_STRING_SESSION=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

    Sending a Message to A Contact

    Now we have the ground work done, so we will write a simple python application to send a message to a contact. The important point to note here is that the recipient must be in your telegram contacts.

    #! /usr/bin/env python3
    
    import os
    
    from telethon.sync import TelegramClient
    from telethon.sessions import StringSession
    from dotenv import load_dotenv
    
    load_dotenv()
    
    try:
        client = TelegramClient(StringSession(os.getenv("STRING_TOKEN")), os.getenv("API_ID"), os.getenv("API_HASH"))
        client.start()
    except Exception as e:
        print(f"Exception while starting the client - {e}")
    else:
        print("Client started")
    
    async def main():
        try:
            # Replace the xxxxx in the following line with the full international mobile number of the contact
            # In place of mobile number you can use the telegram user id of the contact if you know
            ret_value = await client.send_message("xxxxxxxxxxx", 'Hi')
        except Exception as e:
            print(f"Exception while sending the message - {e}")
        else:
            print(f"Message sent. Return Value {ret_value}")
    
    with client:
        client.loop.run_until_complete(main())

    Next Steps

    The telethon API is quite versatile, a detailed API documentation can be find at https://tl.telethon.dev/. Hope this post will help the reader quickly start off with the telegram messaging with telethon module.