Skip to content

Intro

Feed

A collection of short notes, interesting links, and the occasional long form post.

Items

  • Published

    (based on writing https://alexkrupp.typepad.com/sensemaking/2021/06/django-for-startup-founders-a-better-software-architecture-for-saas-startups-and-consumer-apps.html)

    You are an expert in Python, Django, and scalable web application development.

    Predictability

    1. Every Endpoint Should Tell a Story

      • Explanation: Structure each REST API endpoint to follow a consistent pattern. This makes your code predictable and easier to understand. The pattern includes specifying permissions, handling inputs, performing business logic, and returning responses in a standard way.

      • Example:

        from rest_framework.views import APIView
        from rest_framework.permissions import IsAuthenticated
        from rest_framework.response import Response
        
        class UserProfileView(APIView):
            permission_classes = [IsAuthenticated]
        
            def get(self, request):
                # Step 1: Copy input to local variables
                user_id = request.query_params.get('user_id')
        
                # Step 2: Sanitize input
                user_id = sanitize_input(user_id)
        
                # Step 3: Validate input
                if not user_id:
                    return Response({'error': 'User ID is required.'}, status=400)
        
                # Step 4: Enforce business requirements
                if not user_exists(user_id):
                    return Response({'error': 'User does not exist.'}, status=404)
        
                # Step 5: Perform business logic
                user_profile = get_user_profile(user_id)
        
                # Step 6: Return HTTP response
                return Response({'data': user_profile}, status=200)
    2. Keep Business Logic in Services

      • Explanation: Separate your business logic from views and models. Place it in service modules to promote reusability and maintainability.

      • Example:

        # services/user_services.py
        def get_user_profile(user_id):
            # Complex logic to retrieve and process user profile
            pass
        
        # views.py
        from services.user_services import get_user_profile
    3. Make Services the Locus of Reusability

      • Explanation: Reuse service methods across your project wherever the same functionality is needed. This avoids code duplication and keeps your logic consistent.

      • Example:

        # services/notification_services.py
        def send_welcome_email(user_email):
            # Logic to send email
            pass
        
        # Used in multiple places
        send_welcome_email(user_email)
    4. Always Sanitize User Input, Sometimes Save Raw Input, Always Escape Output

      • Explanation: Sanitize all user inputs immediately to prevent security vulnerabilities like XSS attacks. Save raw input only when necessary for future reference, and always escape outputs when displaying data.

      • Example:

        import html
        
        def sanitize_input(input_value):
            return html.escape(input_value)
        
        # Sanitize input
        user_input = sanitize_input(request.data.get('comment'))
    5. Don’t Split Files by Default & Never Split Your URLs File

      • Explanation: Keep related code together to make it easier to find and understand. Avoid unnecessary splitting of files, especially the urls.py file.

      • Example:

        • Keep all URL patterns in a single urls.py file with clear comments separating sections.
        # urls.py
        from django.urls import path
        from .views import UserProfileView, UserSettingsView
        
        urlpatterns = [
            # User-related URLs
            path('user/profile/', UserProfileView.as_view(), name='user-profile'),
            path('user/settings/', UserSettingsView.as_view(), name='user-settings'),
            # Other sections...
        ]

    Readability

    1. Each Variable’s Type or Kind Should Be Obvious from Its Name

      • Explanation: Use clear and descriptive names for variables that indicate their type or purpose, reducing confusion.

      • Example:

        user_list = []
        user_dict = {}
        is_active_user = True
    2. Assign Unique Names to Files, Classes, and Functions

      • Explanation: Ensure that each file, class, and function has a unique name to avoid conflicts and improve searchability.

      • Example:

        • Instead of multiple views.py files, use user_views.py, product_views.py.
    3. **Avoid *args and **kwargs in User Code**

      • Explanation: Use explicit parameters in functions to enhance clarity and prevent unexpected behaviors.

      • Example:

        def create_user(username, email):
            pass  # Good
        
        def create_user(*args, **kwargs):
            pass  # Avoid this
    4. Use Functions, Not Classes

      • Explanation:

        • Simplicity and Clarity: Functions are simpler and more straightforward than classes. They perform specific tasks and are easier to read, understand, and test.
        • Avoid Unnecessary Complexity: Classes introduce complexity with state management, inheritance, and side effects, which can make the code harder to maintain, especially for small to medium-sized projects.
        • Functional Programming Benefits: Emphasizing functions aligns with functional programming principles, leading to more predictable and bug-resistant code.
        • Statelessness: Functions that avoid maintaining state reduce the likelihood of unintended interactions and make concurrent execution safer.
      • Guidelines:

        • Use plain functions for operations that don’t require object-oriented features.
        • Only use classes when you need to maintain state across multiple function calls or when leveraging polymorphism and inheritance is essential.
        • Favor pure functions that return outputs solely based on inputs without side effects.
      • Examples:

        # Good Practice: Using Functions
        
        # utils/math_utils.py
        def calculate_discount(price, discount_percent):
            return price * (discount_percent / 100)
        
        def apply_tax(price, tax_rate):
            return price + (price * (tax_rate / 100))
        
        # Usage in your code
        discounted_price = calculate_discount(original_price, 10)
        final_price = apply_tax(discounted_price, 5)
        # Avoid: Unnecessary Use of Classes
        
        # utils/math_utils.py
        class PriceCalculator:
            def __init__(self, price):
                self.price = price
        
            def calculate_discount(self, discount_percent):
                return self.price * (discount_percent / 100)
        
            def apply_tax(self, tax_rate):
                return self.price + (self.price * (tax_rate / 100))
        
        # Usage in your code
        calculator = PriceCalculator(original_price)
        discounted_price = calculator.calculate_discount(10)
        final_price = calculator.apply_tax(5)

        Why Prefer Functions Over Classes in This Context:

        • Reduced Boilerplate: Functions eliminate the need for boilerplate code like __init__ methods.
        • Easier Testing: Pure functions are easier to test since they don’t rely on or alter external state.
        • Better Readability: Functions focus on performing a single task, making the code more readable and maintainable.
        • Avoiding Side Effects: Without class state, there’s a lower risk of side effects from shared mutable data.
      • When to Use Classes:

        • Stateful Operations: If you need to maintain state between function calls.
        • Complex Data Structures: When modeling entities with both data and behaviors tightly coupled together.
        • Inheritance and Polymorphism: When you need to extend or modify behaviors through inheritance hierarchies.
      • Example of Appropriate Class Use:

        # models/user.py
        class User:
            def __init__(self, username, email):
                self.username = username
                self.email = email
        
            def send_email(self, subject, message):
                # Logic to send email to self.email
                pass
        
        # Using the User class
        user = User('john_doe', '[email protected]')
        user.send_email('Welcome', 'Thank you for signing up!')

        In this case, using a class makes sense because User represents an entity with attributes and behaviors.

    5. There Are Exactly Four Types of Errors

      • Explanation:

        Standardizing error handling across your application improves consistency and makes your code easier to maintain and debug. The four types of errors are:

        1. Upstream Errors:
          • Errors that originate from middleware or external services before reaching your application logic.
          • Handling: Generally, allow these errors to propagate. Customize handling only if necessary.
        2. Validation Errors:
          • Occur when user input fails to meet predefined validation rules (e.g., missing fields, invalid formats).
          • Handling: Collect and return all validation errors together with descriptive messages.
        3. Business Requirement Errors:
          • Occur when input is valid, but the action violates business rules (e.g., insufficient funds, access denied).
          • Handling: Return specific error messages indicating why the action cannot be completed.
        4. Internal Errors (500 Errors):
          • Unexpected errors due to bugs or exceptions in the server.
          • Handling: Log details for debugging but return a generic error message to the client to avoid exposing sensitive information.
      • Guidelines:

        • Use consistent error response structures.
        • Include meaningful error messages and appropriate HTTP status codes.
        • Avoid exposing internal implementation details in error messages sent to clients.
        • Implement centralized error handling where possible to maintain consistency.
      • Examples:

        from rest_framework.views import APIView
        from rest_framework.response import Response
        from rest_framework import status
        
        class TransactionView(APIView):
            permission_classes = [IsAuthenticated]
        
            def post(self, request):
                # 1. Copy and sanitize inputs
                amount = sanitize_input(request.data.get('amount'))
                recipient_id = sanitize_input(request.data.get('recipient_id'))
        
                # 2. Validate inputs
                errors = {}
                if not amount:
                    errors['amount'] = ['Amount is required.']
                elif not is_valid_amount(amount):
                    errors['amount'] = ['Invalid amount format.']
        
                if not recipient_id:
                    errors['recipient_id'] = ['Recipient ID is required.']
        
                if errors:
                    # **Validation Error**
                    return Response({'errors': errors}, status=status.HTTP_400_BAD_REQUEST)
        
                # 3. Business logic and requirements
                if not has_sufficient_funds(request.user, amount):
                    # **Business Requirement Error**
                    return Response(
                        {'error': 'Insufficient funds.'},
                        status=status.HTTP_403_FORBIDDEN
                    )
        
                if not recipient_exists(recipient_id):
                    return Response(
                        {'error': 'Recipient not found.'},
                        status=status.HTTP_404_NOT_FOUND
                    )
        
                try:
                    # 4. Perform transaction
                    transaction = perform_transaction(request.user, recipient_id, amount)
                except ExternalServiceError as e:
                    # **Upstream Error**
                    return Response(
                        {'error': 'Transaction service is unavailable.'},
                        status=status.HTTP_503_SERVICE_UNAVAILABLE
                    )
                except Exception as e:
                    # **Internal Error**
                    log_error(e)
                    return Response(
                        {'error': 'An unexpected error occurred.'},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR
                    )
        
                # 5. Success response
                return Response({'message': 'Transaction completed successfully.'}, status=status.HTTP_200_OK)
      • Detailed Breakdown:

        • Validation Errors (400 Bad Request):

          • Collect all input validation errors.
          • Return them together so the client can fix all issues at once.
          if errors:
              return Response({'errors': errors}, status=status.HTTP_400_BAD_REQUEST)

          Error Response Structure:

          {
            "errors": {
              "amount": ["Amount is required."],
              "recipient_id": ["Recipient ID is required."]
            }
          }
        • Business Requirement Errors (403 Forbidden, 404 Not Found):

          • Return a specific error message indicating the business rule violation.
          • Use appropriate status codes to reflect the error type.
          if not has_sufficient_funds(request.user, amount):
              return Response(
                  {'error': 'Insufficient funds.'},
                  status=status.HTTP_403_FORBIDDEN
              )

          Error Response Structure:

          {
            "error": "Insufficient funds."
          }
        • Upstream Errors (503 Service Unavailable):

          • Errors from external services or middleware.
          • Communicate service unavailability without exposing internal details.
          except ExternalServiceError as e:
              return Response(
                  {'error': 'Transaction service is unavailable.'},
                  status=status.HTTP_503_SERVICE_UNAVAILABLE
              )
        • Internal Errors (500 Internal Server Error):

          • Catch-all for unexpected exceptions.
          • Log the exception details internally.
          • Return a generic error message to the client.
          except Exception as e:
              log_error(e)
              return Response(
                  {'error': 'An unexpected error occurred.'},
                  status=status.HTTP_500_INTERNAL_SERVER_ERROR
              )
      • Consistent Error Response Structure:

        • For Validation Errors:

          {
            "errors": {
              "field_name": ["Error message."]
            }
          }
        • For Business Requirement, Upstream, and Internal Errors:

          {
            "error": "Error message."
          }
      • Best Practices:

        • Logging:
          • Always log exceptions and errors on the server side for auditing and debugging purposes.
        • User-Friendly Messages:
          • Provide clear and actionable error messages without exposing sensitive information.
        • HTTP Status Codes:
          • Use appropriate status codes to represent the error type (e.g., 400, 403, 404, 500).
        • Avoid Exception Swallowing:
          • Do not catch exceptions without handling them properly. This could mask issues and make debugging difficult.
        • Error Handling Middleware:
          • Implement middleware to handle uncaught exceptions globally, ensuring consistent error responses.

    Simplicity

    1. URL Parameters Are a Scam

      • Explanation: Avoid using URL parameters. Use query parameters for GET requests and body parameters for POST/PUT requests for consistency.

      • Example:

        # Instead of this
        path('user/<int:user_id>/', UserProfileView.as_view(), name='user-profile')
        
        # Use this
        path('user/profile/', UserProfileView.as_view(), name='user-profile')
        # And pass user_id as a query parameter: /user/profile/?user_id=123
    2. Write Tests. Not Too Many. Mostly Integration.

      • Explanation: Focus on writing integration tests for your endpoints to ensure they work correctly when combined. Don’t overdo unit tests.

      • Example:

        from rest_framework.test import APITestCase
        
        class UserProfileTests(APITestCase):
            def test_get_user_profile(self):
                response = self.client.get('/user/profile/', {'user_id': 1})
                self.assertEqual(response.status_code, 200)
    3. Treat Unit Tests as a Specialist Tool

      • Explanation: Use unit tests sparingly for complex functions or algorithms that need isolated testing.

      • Example:

        import unittest
        
        def calculate_discount(price, percentage):
            return price * (percentage / 100)
        
        class DiscountTests(unittest.TestCase):
            def test_calculate_discount(self):
                self.assertEqual(calculate_discount(100, 10), 10)
    4. Use Serializers Responsibly, or Not at All

      • Explanation: Use serializers for input validation and output formatting, but avoid overcomplicating them. Consider simpler alternatives when appropriate.

      • Example:

        from rest_framework import serializers
        
        class UserSerializer(serializers.Serializer):
            username = serializers.CharField(max_length=100)
            email = serializers.EmailField()
    5. Write Admin Functionality as API Endpoints

      • Explanation: Implement admin actions as API endpoints with proper permissions. This keeps logic consistent and testable.

      • Example:

        # views.py
        class AdminUserListView(APIView):
            permission_classes = [IsAdminUser]
        
            def get(self, request):
                users = get_all_users()
                return Response({'users': users}, status=200)

    Upgradability

    1. Your App Lives Until Your Dependencies Die

      • Explanation: Choose well-maintained and widely-used dependencies. Keep them updated to ensure long-term sustainability.

      • Example:

        • Use the latest stable versions of Django and other libraries.
        • Regularly update dependencies using pip-upgrade.
    2. Keep Logic Out of the Front End

      • Explanation: Minimize business logic in the front end. Handle it on the server side to simplify maintenance and upgrades.

      • Example:

        • Perform data processing in views or services, and send prepared data to the front end.
    3. Don’t Break Core Dependencies

      • Explanation: Avoid altering or making assumptions about the internal behavior of core frameworks and libraries. Use them as intended.

      • Example:

        • Don’t override internal Django methods unless absolutely necessary.
        • Avoid hacking into library internals to change behavior.

    Using uv for the project, and to run manage.py use uv run python manage.py etc

  • Published

    …if anything I want to become what I’ve called an information trillionaire, I’m not going to make that but its a good aspiration to have, just collect more information and be an information trillionaire…

    from https://www.youtube.com/watch?v=W1eEPAUE6nY

  • Published

    canScroll() { return false } wasn’t working on my custom shape. Did this instead.

    const TableComponent = () => {
      const containerRef = useRef<HTMLDivElement>(null);
    
      useEffect(() => {
        const containerElement = containerRef.current;
    
        const handleWheel = (event: WheelEvent) => {
          const isOverCardShape = containerElement?.contains(event.target as Node);
    
          // to determine if the event should be prevented
          if (isOverCardShape) {
            // event.preventDefault();
            event.stopPropagation();
          }
        };
    
        containerElement?.addEventListener("wheel", handleWheel, {
          passive: false,
        });
    
        return () => {
          containerElement?.removeEventListener("wheel", handleWheel);
        };
      }, [containerRef]);
    
      ...
  • Published
    import { DayPicker } from "react-day-picker";
    import "react-day-picker/style.css";
    
    function MyDatePicker() {
      const [selected, setSelected] = useState<Date>();
    
      return (
        <DayPicker
          mode="single"
          selected={selected}
          onSelect={setSelected}
          footer={
            selected ? `Selected: ${selected.toLocaleDateString()}` : "Pick a day."
          }
        />
      );
    }

    DayPicker + Radix UI Popover

    "use client";
    import React from "react";
    import { useState } from "react";
    import { DayPicker } from "react-day-picker";
    import * as Popover from "@radix-ui/react-popover";
    import { format } from "date-fns";
    import "react-day-picker/style.css";
    import { useRouter } from "next/navigation";
    
    export function MyDatePicker({ displayDate }: { displayDate: string }) {
      const router = useRouter();
    
      const [selected, setSelected] = useState<Date>();
      const [open, setOpen] = useState(false);
    
      const handleDateSelect = (date: Date) => {
        setSelected(date);
        const formattedDate = format(date, "yyyy-MM-dd");
        router.push(`/schedule?date=${formattedDate}`);
        setOpen(false);
      };
    
      return (
        <Popover.Root open={open} onOpenChange={setOpen}>
          <Popover.Trigger asChild>
            <button className="display-date">{displayDate}</button>
          </Popover.Trigger>
          <Popover.Content className="PopoverContent">
            <DayPicker
              mode="single"
              selected={selected}
              onDayClick={handleDateSelect}
              footer={
                selected
                  ? `Selected: ${selected.toLocaleDateString()}`
                  : "Pick a day."
              }
            />
          </Popover.Content>
        </Popover.Root>
      );
    }
    
  • Published

    Astro + Temporal try

    This code demonstrates how to initiate a workflow in Astro using the Temporal client