Monday, 8 December 2025

Scarcity Brings Efficiency: Python RAM Optimization


 

In today’s world, with the abundance of RAM available, we rarely think about optimizing our code. But sooner or later, we hit the limits and realize it a bit too late. This blog explores how we can make better use of RAM by applying various Python optimization techniques.

Memory Usage Stats

Let's begin with how to measure the amount of RAM used by a python program.  As there are always more than one ways to perform an action and each has its merits.

Using sys.getsizeof:

This is a built-in function from the sys module that returns the shallow size of a single Python object in bytes.

  • What it measures: Only the memory directly attributed to the object itself (e.g., the list structure), excluding the size of any referenced objects (e.g., the integers or strings inside a list).
  • Pros:
    • Simple, fast and lightweight.
    • Useful for quick comparisons of basic object overhead (e.g., empty list vs. small list).
  • Cons/Limitations:
    • Shallow only as it underestimates containers like lists, dicts, or custom objects with references values in them.
    • As this is not recursive, it doesn't help with complex structures or total memory footprint.
  • When to use: For basic, one-off checks on simple objects or understanding Python's object overhead. Not suitable for profiling real memory usage in applications.
import sys

def get_object_size_mb(obj):
"""
Returns the size of a Python object in megabytes (MB).
"""
size_bytes = sys.getsizeof(obj)
size_mb = size_bytes / (1024 * 1024) # Convert bytes to MB
return size_mb

# create huge list
a = [i for i in range(5_000_000)]
size_of_list_mb = get_object_size_mb(a)
print(f"Size python object is: {size_of_list_mb:.2f} MB")

##OUTPUT
% /usr/bin/python3 getsizeof_sample.py Size python object is: 38.35 MB


Using tracemalloc:

A built-in standard library module (since Python 3.4) for tracing Python memory allocations.

  • What it measures: Tracks memory blocks allocated by Python, providing detailed statistics (size, count, traceback) grouped by line, file, or traceback. Take snapshots and compare them to find leaks or differences.
  • Pros:
    • No external dependencies.
    • Precise for Python-allocated memory (lower overhead than some tools).
    • Excellent for debugging leaks: shows exactly where allocations happen.
    • Supports peak/current traced memory and object-specific tracebacks.
  • Cons:
    • Only tracks Python allocations (misses C extensions like NumPy unless they use Python's allocator).
    • Requires starting tracing early (ideally at program start).
    • More low-level; needs code to take/compute snapshots.
  • When to use: Investigating memory leaks easily, finding allocation hotspots, or comparing memory before/after code sections. Great for detailed, programmatic analysis in production-like debugging.
import tracemalloc

tracemalloc.start()

# create huge list
a = [i for i in range(5_000_000)]

current, peak = tracemalloc.get_traced_memory()
print(f"Current memory: {current / 10**6:.2f} MB; Peak: {peak / 10**6:.2f} MB")

tracemalloc.stop()

##OUTPUT
% /usr/bin/python3 tracemalloc_sample.py
Current memory: 180.21 MB; Peak: 180.21 MB


Using memory_profiler:

A third-party package (pip install memory_profiler) for line-by-line memory profiling.

  • What it measures: Memory usage (typically Resident Set Size - is the portion of a process that is actually living in RAM right now) after each line in decorated functions, showing increments. Uses psutil as the default backend (optional switch to tracemalloc for more precise Python-only allocations)
  • Pros:
    • Easy line-by-line view (similar to line_profiler for time).
    • Works cross-platform (Windows/Linux/Mac).
    • Plots memory over time with mprof.
    • Backend flexible (can switch between psutil and tracemalloc).
  • Cons:
    • External dependency (and often needs psutil).
    • Higher overhead due to frequent sampling of process memory.
    • RSS is coarse and may overestimate memory (includes shared libs, OS allocations). 
    • Less precise than tracemalloc for pinpointing pure Python object allocations.
  • When to use: Quick, high-level overview of memory growth per line in functions/scripts. Ideal for exploratory profiling for scripts or functions where you only need to know which lines are causing memory jumps, not deep allocation traces.
from memory_profiler import memory_usage

def measure_ram(func):
def wrapper(*args, **kwargs):
mem_before = memory_usage()[0]
result = func(*args, **kwargs)
mem_after = memory_usage()[0]
print(f"RAM used: {mem_after - mem_before:.2f} MB")
return result
return wrapper

@measure_ram
def sample_func():
a = [i for i in range(5_000_000)]

sample_func()

##OUTPUT
% /usr/bin/python3 memory_profiler_sample.py
RAM used: 3.60 MB

Memory Usage Techniques


Effective Data Structure Usage

Choosing the right data structure is one of the most important ways to reduce RAM usage and improve performance. Different structures store data differently and have different memory footprints.

Array Vs List: Using a regular list to store many numbers is much less efficient in RAM than using an array object. More memory allocations have to occur, which each take time, calculations also occur on larger objects, which will be less cache friendly, and more RAM is used overall, so less RAM is available to other programs.

from memory_profiler_sample import measure_ram #Note: imported function is from above sample
from array import array

# Create many integers
N = 5_000_000

@measure_ram
def sample_list():
print("List Creation")
data_list = list(range(N))

sample_list()

@measure_ram
def sample_arr():
print("Array Creation")
data_array = array('i', range(N))

sample_arr()

##OUTPUT
% /usr/bin/python3 list_VS_arr_sample.py
List Creation
RAM used: 41.45 MB
Array Creation
RAM used: 19.16 MB

Tuple Vs List: Tuples use less memory as there is it cannot be resized and no extra capacity reserved.  Faster and more cache-friendly. Similar behaviour goes with set vs dict.

Choose the Smallest Numeric Type: When working with large datasets in Python, NumPy arrays are far more memory-efficient than pure Python lists or Pandas Series (which often default to higher-precision types). The key is to select the smallest dtype that can safely represent your data without overflow or loss of precision. Here are few options int64 uses 8 bytes,  int32 uses 4 bytes, int16 uses 2 bytes.
 
Use Sparse Data Structures: Use libraries like, scipy.sparse or pandas.SparseArray, when the data holds most values as zero, instead storing all zeros does not makes effective use of RAM. Hence in the sparse matrices store only non-zero entries.


Bytes versus Unicode

Python distinguishes between text (human-readable characters) and binary data (raw bytes - images, files, network packets, compressed data, encrypted data). Understanding the difference is essential for file handling, networking, APIs, and encoding issues. Unicode strings use more memory because it requires to store character metadata.  Bytes are compact as it uses only 1 byte per element.

import sys

def get_object_size_mb(obj):
"""
Returns the size of a Python object in megabytes (MB).
"""
size_bytes = sys.getsizeof(obj)
size_mb = size_bytes / (1024 * 1024) # Convert bytes to MB
return size_mb

# Create N string
N = 500_000_000

s = "hello 😊"

# Unicode string (each character may take multiple bytes)
def sample_text():
print("Text Creation")
data_text = s*N
print(f"Size is: {get_object_size_mb(data_text):.2f} MB")

sample_text()

# Bytes version (UTF-8 encoded)
def sample_bytes():
print("Bytes Creation")
data_text = s*N
data_bytes = data_text.encode("utf-8")
print(f"Size is: {get_object_size_mb(data_bytes):.2f} MB")

sample_bytes()



##OUTPUT
% /usr/bin/python3 text_VS_unicode_sample.py
Text Creation
Size is: 13351.44 MB
Bytes Creation
Size is: 4768.37 MB



Huge Text data store

Handling massive text data (logs, documents, transcripts, crawled data, chat history, etc.) requires storage formats and structures that are memory-efficient, compressed, and scalable.

Use Compression (gzip, bz2, zstd): Making use of compression helps in text compresses 10–30× smaller, as storing raw text wastes huge amounts of space.

mmap: Map a file (or part of it) directly into memory and treat gigabytes of data as if it were a giant string or byte array. Super fast for large files and low memory usage. Giving a bytes-like object that can be terabytes in size, but uses almost no RAM until you touch the pages, resulting no RAM explosion.

DAWG (Directed Acyclic Word Graph): A DAWG is a highly compressed data structure used to store large sets of strings efficiently. It is especially powerful for dictionaries, NLP vocabularies, autocomplete engines, and prefix-based searches. 

import dawg

words = ["cat", "car", "cart", "dog", "doing"]

d = dawg.CompletionDAWG(words)

print(d.keys("ca")) # prefix search: ['cat', 'car', 'cart']
print("dog" in d) # True

Tries: Refers to a tree-based data structure also known as a prefix tree or digital tree. It is used to efficiently store and retrieve a dynamic collection of strings by sharing common prefixes. Prefix-based string data structures play a crucial role in text indexing, NLP, search engines, auto-complete systems and large dictionary storage.
Below are few commonly used structures are:
  • Marisa-Trie (Matching Algorithm with Recursively Implemented StorAge)
    • Static compressed trie using Louds (Level-Order Unary Degree Sequence) representation. 
    • Extremely small memory footprint (often 5–10% of original text). 
    • Supports predictive search (common prefix search) and exact-match very efficiently. 
    • Once built, the structure is read-only. Hence not suitable for frequent insertions/deletions.
    • Python module: marisa-tri.
  • Double-Array Trie (DAT)
    • The Double-Array Trie (DAT) is a compact Trie representation using two arrays: 
      • BASE array
        • determines offset for child transitions
        • base[i]: starting index for children of node i
      • CHECK array
        • stores the parent index to validate transitions
        • check[i]: parent node of node i (for collision detection)
    • Very fast transitions (just array indexing). 
    • Supports dynamic insertion/deletion (though slower than lookup).
    • Python module: datrie
  • HAT-Trie (Hash-Array mapped Trie)
    • A HAT-Trie combines:  
      • A Trie for prefix organization
      • Hash buckets for fast leaf-node lookups
    • Starts with a hash table at the top levels, then “bursts” into small trie containers when buckets fill. 
    • Extremely cache-efficient because burst containers are small and contiguous. 
    • Supports insertion, deletion, prefix search, and can store values/frequencies.
    • Python module: hat-trie


General Tips

Avoiding Copies: Avoid making unnecessary copies of the objects, which will occupy RAM with redundant data. Instead use references, or views from NumPy or use iterators. 
Note: For strings Python reuses identical immutable strings, this avoids storing the same string many times.

Profiling: Like shown above use the profiling methods to check memory usage while programming. These will help to detect early issues, like excessive allocations, memory leaks, inefficient structures.

Use Generators Instead of Storing Everything in Memory: A generator yields one item at a time as there here we would not need to store entire dataset in RAM.

Numpy: While working with numeric data, opt in to use numpy arrays as they offers many fast algorithms. 

Bitarray: If you're code requires lots of bit strings, numpy and bitarray packages both provide efficient representation of bits packed into bytes.

Micro Python: Will be interesting project for working with embedded sytems, its a tiny memory footprint allows developers to write high-level, readable Python code while still running on hardware traditionally restricted to low-level C/C++. It brings much of the core Python 3 functionality to devices with extremely limited resources—often with as little as 16–256 KB of RAM and flash storage.

Unit Test Cases: In the aim of optimization one must not forget the fundamentals - the purpose of the code. Hence make sure to have unit test suite in place before you make algorithmic changes.


Conclusion

Just to recap, profiling helps you see the problem; effective structures and techniques help you fix it. Together, they form a solid foundation for writing memory-efficient, high-performance Python programs. The general tips practices help improve performance, reduce memory pressure, and make applications more scalable—especially when working with large datasets or resource-constrained environments.


References:

High Performance Python - O'Reilly Book
LLMs for making the topics presentable

Thursday, 24 October 2024

GraphQL

 


Introduction

API is the abbreviation of Application Programming Interface. It means when two or more applications talk to each other they would use this intermediary program. Web API is an API which can be accessed over internet which is usually using HTTP or HTTPS protocol. GraphQL is one of its kind running over HTTP/HTTPS. (but its not hard and fast rule, it can be on any protocol)

Let me re-confirm GraphQL is not a protocol it is specification for client-server communication. It means specification is a description provided how client-server talks to understand each other, in lay man term its how we speak a language for our communication.

GraphQL is on the Web API kind. GraphQL is one of the most modern ways of building and querying APIs given the input in JSON format. It helps with a runtime for fulfilling those queries with your existing data. 

Internet is all around right from system to watch, each serve different set of users serving different demands. Let's imagine we need to fulfil similar request but with different volume data based on the client. It would be inappropriate if we give less/underfetching or more/overfetching information to all. As a saviour GraphQL comes to serve clients only for the asked information without affecting coding changes in the server. 

GraphQL is originally developed to query server hence its called GraphQL - Graph Query Language. One might wonder then why it is Graph, well the information stored in hierarchically is know as graph and using GraphQL you can drill down to every connection and get the node information (we will see below with an example).

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools (GraphiQL).

Facebook's mobile apps have been powered by GraphQL since 2012.





Today we will see about GraphQL, its features, pros and cons. 

GraphQL Features

  • GraphQL communication

    • GraphQL provides standard three ways of communication :

      • schema

      • query

      • response

  • GraphQL schema

    • Describe data provided by a server in a statically typed Schema.

  • GraphQL query

    • A GraphQL query is a string that is sent to a server to be interpreted and fulfilled, which then returns JSON back to the client.

  • GraphQL response

    • GraphQL query from the API will get exactly what is asked, nothing more nothing less.

    • GraphQL queries mirror their response based on the query asked. This makes it easy to predict the shape of the data returned from a query, as well as to write a query if you know the data your app needs. 

  • GraphQL performance impact

    • Applications using GraphQL are fast and stable because the data is controlled by them not the server.

    • Its hierarchical nature, naturally follows relationships between objects, where a RESTful service may require multiple round-trips.

  • GraphQL single trip

    • GraphQL APIs get all the data your app needs in a single request.

    • Apps using GraphQL can be quick even on slow mobile network connections, as multiple trips are avoided to fetch data from server.

  • GraphQL type system

    • Each level of a GraphQL query corresponds to a particular type, and each type describes a set of available fields.

    • GraphQL uses types to ensure Apps only ask for what’s possible and provide clear and helpful errors.

    • Apps can use types to avoid writing manual parsing code.

  • GraphQL IDE

    • GraphiQL is the GraphQL integrated development environment (IDE)

    • GraphiQL helps developers and api users learn and explore an API quickly without grepping the codebase or struggling with cURL

    • Easily you can access it when your site’s development server is running at http(s)://<ip>:<port>/graphql (Python version)

  • GraphQL versioning

    • When you’re adding new product features, additional fields can be added to the server, leaving existing clients unaffected.

    • By using a single evolving version, GraphQL APIs give apps continuous access to new features and encourage cleaner, more maintainable server code.

  • GraphQL Integrations

    • GraphQL can be easily integrated with the below packages:

      • Graphene-Django

      • Flask-Graphql

      • Graphene-SQLAlchemy

      • Graphene-GAE

      • Graphene-Mongo

      • Starlette

      • FastAPI

    • We will below examples using the Flask integration.

  • GraphQL operations

    • Query: GraphQL Query is used to read or fetch values.

    • Mutation: GraphQL Mutation queries modify data in the data store and returns a value. It can be used to insert, update, or delete data.

    • Subscription: Subscriptions are a GraphQL feature that allows a server to send data to its clients when a specific event happens.



Requirements and Installation

To start using graphql, we would need python 3+ version.

Python >= 3.4

Graphene (3.0) is the module used to the graphql.

Using pip you can easily install graphene.

	pip install "graphene>=3.0"



GraphQL Structure

Fields

GraphQL Fields are generally the columns specified, which could be called in querying the values. GraphQL provides different types of Fields, such as ID, String, Scalar, List, Enum, Object, Interface and Unions.

Eg: 

Query:

projects {
    ProjectID
    ProjectName
    LOBDetails {
        LOBID
        LOBName
    }
}

Output:

{
    “data”: {
        projects:
        {
            ProjectID: “J1”
            ProjectName: “Jio”
            LOBDetails:
            {
                LOBID: “AT”
                LOBName: “Atul”
            }
            {
                LOBID: “BA”
                LOBName: “Bavesh”
            }
        }
    }
}

From the above example, we have returned 1 project which is headed by 2 LOBs. Here the LOBID is ID data type and LOBName is string datatype. There would be other fields also but GraphQL will return only the asked details.

Resolver

Resolver is ideally a function associated with field which generates the response in JSON format for the GraphQL Query. And it would be only called in the query is requesting to view the value of the field. In the above example we have LOBDetails information which would be resolved while its called. Lets see the syntax with example code. Note the function should always start with resolve_<FieldName>

resolve_fieldName:(root, args, context, info) => { result }


Eg:

class Project(graphene.ObjectType):
    LOBID = graphene.ID(required=True)
    ProjectID = graphene.ID(required=True)
    ProjectName = graphene.String()
    ProjectDesc = graphene.String()
    LOBDetails = graphene.List(LOB)

    def resolve_LOBDetails(root, info):
        lobid = root.get('LOBID', None)
        return db.get_lob(lobid)



Schema

The GraphQL Schema is where the data model is described. GraphQL server provides set of resolve methods that know how and from where to fetch the data. Schema will be defined once in the program, which will collect all the type definitions. Later down we have an example, filename called schema.py which holds the sample schema definition. 


Query

GraphQL Query can be said as Read operation. This where you can specify or we can query the API Fields. Even though query seems to be GET operation, but the actual POST HTTP Method is called for this. Class graphene.ObjectType needs to be inherited for framing the Query. Below we have specified the syntax of using query:

//syntax 1
query query_name{ someField }

//syntax 2
{ someField }


Mutation

GraphQL Mutation can be said as Write, Update and Delete operations. Here you will specify the values which needs to be changed, and response after performing the operation. Below we have specified the syntax of using query:

mutation{
   someEditOperation( someInputSchema: 
	{	
		dataField1:"valueOfField1",
		dataField2:"valueOfField2"
	}
    ) {
      returnValue1
      returnValue2
   }
}


Validation

Its one of the very useful feature provided by the module, any input either while querying or mutating the values the validation will be done. Validation such as not null, datatype validation, string length, range, and so on. Below we have any example where the LOBID is mandatory to be provided while insertion if not operation will fail.


Enum

GraphQL supports Enumerations. An enumeration is a set of symbolic names which are bound to unique and having constant values. In graphene we can add description as well to the Enum values using @property with description method.

Eg:

# Enum Definition

class Rating(graphene.Enum):
    EXCELLENT = 1
    GOOD = 2
    OKAY = 3

    @property
    def description(self):
        if self == Rating.EXCELLENT:
            return 'Very Good Performance'
        if self == Rating.GOOD:
            return 'Good Performance'
        return 'Okay Performance'
# Enum Usage as Field
class UpdateEmployee(graphene.Mutation):
	EmpID = graphene.ID(required=True)

EmpRating = Rating(required=True)



Interface

As like general language Interface in GraphQL also provides similar feature. It defines the abstract type that contains certain fields which can be later inherited to other Schemas. For example we have few common fields which we can define in single interface and use it across the respective schemas. Also we would need to write an resolver for this while querying.

Eg:

import graphene


Creation of Interface called AuditInformation

class AuditInformation(graphene.Interface):

CreatedBy = graphene.String()

ModifiedBy = graphene.String()

CreatedOn = graphene.Date()

# Need to define a resolve_type class method, else will return error. This is to map the interface date object to graphene.type

# Error: "Abstract type XXX must resolve to an Object type at runtime for field..."

# https://docs.graphene-python.org/en/latest/types/interfaces/#resolving-data-objects-to-types

@classmethod

def resolve_type(cls, instance, info):

if instance["Type"] == 'Project':

return Project

return LOB



# Calling the Interface as AuditDetails in LOB Schema

class LOB(graphene.ObjectType):

AuditDetails = graphene.Field(AuditInformation)

LOBID = graphene.ID(required=True)

LOBName = graphene.String()

 

def resolve_AuditDetails(root, LOBID): 

    return get_audit_details(LOBID)



# Calling the Interface as AuditDetails in Project Schema

class Project(graphene.ObjectType):

AuditDetails = graphene.Field(AuditInformation)

ProjectID = graphene.ID(required=True)

def resolve_AuditDetails(root, LOBID, ProjectID):

return get_audit_details(LOBID, ProjectID)

Lets see how to query the interfaces:

Query:

query{

    project{

        ProjectID

        LOBID

        AuditDetails{

            CreatedBy

            ModifiedBy

            __typename

        }

    }

}


Result:

{

    "data": {

        "project": [

        {

            "ProjectID": "Proj1",

            "LOBID": "123",

            "AuditDetails": {

                "CreatedBy": "Admin",

                "__typename": "Project"

            }

        },

        {

            "ProjectID": "Proj2",

            "LOBID": "123",

            "AuditDetails": {

                "CreatedBy": "Admin",

                "__typename": "Project"

            }

        }

    }

}


Union

Unions are like we have in dbs, but not that we will have combine different values in single row but still the values are get build in multiple rows. If you want to return more than one type of object they you can create union type, which creates an association between two different object types. In graphene package it must inherit from graphene.Union. Unions don’t have any fields on it, just links to the possible graphene.ObjectType.

Eg:

class Project(graphene.ObjectType):

class Meta:

interfaces = (AuditInformation, )

LOBID = graphene.ID(required=True)

ProjectID = graphene.ID(required=True)

ProjectName = graphene.String()

ProjectDesc = graphene.String()

LOBDetail = graphene.List(LOB)

def resolve_LOBDetail(root, info):

lobid = root.get('LOBID', None)

return db.get_lob(lobid)

AuditDetails = graphene.Field(AuditInformation)

def resolve_AuditDetails(root, info):

return get_audit(root, info)


class POC(graphene.ObjectType):

class Meta:

interfaces = (AuditInformation, )

LOBID = graphene.ID(required=True)

POCID = graphene.ID(required=True)

POCName = graphene.String()

POCDesc = graphene.String()

LOBDetail = graphene.List(LOB)

def resolve_LOBDetail(root, info):

lobid = root.get('LOBID', None)

return db.get_lob(lobid)

AuditDetails = graphene.Field(AuditInformation)

def resolve_AuditDetails(root, info):

return get_audit(root, info)


class LOBSolutions(graphene.Union):

class Meta:

types = (LOB, Project)


@classmethod

def resolve_type(cls, instance, info):

print(instance)

if instance.get("POCID"):

return POC

return Project


Lets see how to query the Unions:

Query:

query{

    lobsolutions{

        ... on Project{

            ProjectID

            ProjectName

         },

        ... on POC {

            POCID

            POCName

            LOBDetail{

                LOBID

                LOBName

            }

        }

    }

}



Results

{

    "data": {

        "lobsolutions": [

            {

                "ProjectID": "Proj1",

                "ProjectName": "Project"

            },

            {

                "ProjectID": "Proj2",

                "ProjectName": "Project"

            },

            {

                "POCID": "POCProj1",

                "POCName": "POCProject",

                "LOBDetail": [

                    {

                        "LOBID": "123",

                        "LOBName": "Test"

                    }

                ]

            },

            {

                "POCID": "POCProj2",

                "POCName": "POCProject",

                "LOBDetail": [

                    {

                        "LOBID": "123",

                        "LOBName": "Test"

                    }

                ]

            }

        ]

    }

}


GraphQL Example

Easiest way to learn is through understand different examples. Lets try that out. Here we will try to create lob (line of business) information.


Source Code:

File: lob_schema.py

Description: This file holds the information about LOB schema.

File Contents:

import lob_db


# Schema Objects

# Inherited by graphene.ObjectType will be used by Query

class LOB(graphene.ObjectType):

# ID : Is a datatype, which refers to the primary key

# required=True -> Makes the LOBID mandatory to be given from the client

LOBID = graphene.ID(required=True)

LOBName = graphene.String()

LOBDesc = graphene.String()


# Inherited by graphene.InputObjectType will be used by Mutation

class LOBInput(graphene.InputObjectType):

LOBID = graphene.ID(required=True)

LOBName = graphene.String()

LOBDesc = graphene.String()



class CreateLOB(graphene.Mutation):

class Arguments:

lob_input = LOBInput(required=True)


ok = graphene.Boolean()

msg = graphene.String()

def mutate(root, info, lob_input=None):

ok, msg = db.insert_lob(lob_input)

return CreateLOB(ok=ok, msg=msg)



File: lob_db.py

Description:

  • This file holds database operations for lob

  • In the below example LOBS are saved in the array of dict

  • But in real use case it could be any database

  • But the return value should match the keys of our defined Schema

File Contents:

LOBS = [

    {

        'LOBID': "123",

        'LOBName': "Test",

        'LOBDesc': "Test Desc"

    } 

]


def get_lob(lobid=None):

    result = LOBS

    if(lobid):

        result = filter(lambda lob: lob['LOBID'] == lobid, LOBS)

    return result


def insert_lob(lob):

    print(lob)

    LOBS.append(lob)

    return True, "Success"



File: schema.py

Description:

  • This file holds the maps between all the schema in one file.

  • We can have more than one sub schemas defined but will link here.

  • createlob is a field returned from CreateLOB 

File Contents:

import lob_schema

import lob_db


class Mutation(graphene.ObjectType):

    createlob = lob_schema.CreateLOB.Field()


class Query(graphene.ObjectType):

    lob = graphene.List(lob_schema.LOB)

    def resolve_lob(root, info):

        return lob_db.get_lob()


schema = graphene.Schema(query=Query, mutation=Mutation)



File: app.py

Description:

  • This file defines the application's starting point

  • GrahpQL here is integrated with Flask

  • Flask server will run

  • /grahpql path will be used both for Query and Mutation

  • For both it will be POST Method


File Contents:

from flask import Flask

from flask_graphql import GraphQLView

from schema import schema


app = Flask(__name__)

app.debug = True


@app.route('/')

def index():

return '<p> Hello World</p>'


app.add_url_rule(

'/graphql',

view_func=GraphQLView.as_view(

'graphql',

schema=schema,

graphiql=True # for having the GraphiQL interface

)

)


if __name__ == '__main__':

    app.run()



Execution GraphQL Server:

Execute: app.py

% python app.py 

* Serving Flask app 'app' (lazy loading)

* Environment: production

WARNING: This is a development server. Do not use it in a production deployment.

Use a production WSGI server instead.

* Debug mode: on

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

* Restarting with watchdog (fsevents)

* Debugger is active!





Output:

Step 1: 

  • Open the URL with the given hostname and port

  • Check the “/” path 

  • From the source code it will print Hello World

Fig1: Root path, as per code it prints Hello World


Step 2:

  • Open the /graphql path. 

  • This is the same path configured in the app.py, this can be any name though

  • Below will shown only if the graphiql is enabled in app.py

Fig 2: graphql path, shows Query and Mutation


Step 3:

  • Lets check the available Mutation, in our scenario we will have createlob

  • LOBInput is the values required to be inserted/updated/deleted - mutated


Fig3: createlob is listed specifying its input and return value


Step 4:

  • Lets see what values in the LOBInput requires

  • The “!” Value next to ID means required


Fig4: Input values for LOB 


Step 5:

  • Query the current values available in the LOB

  • As you could see we have only one record

Fig5: Query output of LOB 


Step 6:

  • Mutation called to perform insert

  • Here LOBID and LOBName values are specified to be inserted

  • LOBDesc is not given, which will be considered as NULL.

Fig6: Mutation query to insert


Step 7:

  • After insert lets query again and see the output.

  • Inserted row should be visible.

  • List of executed queries will also be recorded in the History

  • Necessary fields can be used to extract.

Fig7: After Mutation, Query output




GraphQL Vs REST

  • Below is very good reference written stating the difference of GraphQL and REST

Fig8 : GrapqhQL vs REST

Credits: https://www.mobilelive.ca/blog/graphql-vs-rest-what-you-didnt-know/



GraphQL Pros

  • A GraphQL schema offers an organisation a way to federate its entire API.

  • GraphQL calls are handled in a single round trip. Clients get what they request with no over fetching.

  • Strongly defined data types reduce miscommunication between the client and the server. 

  • GraphQL allows an application API to evolve without breaking existing queries.



GraphQL Cons

  • GraphQL presents a learning curve for developers familiar with REST APIs.

  • GraphQL shifts much of the work of a data query to the server side, which adds complexity for server developers.

  • Caching is more complex than with REST.

  • No standard HTTP error codes supported.

  • URLs cannot be bookmarked, as the query is passed in the POST.

  • API maintainers have the additional task of writing maintainable GraphQL schema.



Conclusion

We have seen a very basic example of GraphQL. But it holds lot more concepts such as Enum, Subscription, Interfaces, Unions, etc. Below references provide very good insights in such concepts.


References


Scarcity Brings Efficiency: Python RAM Optimization

  In today’s world, with the abundance of RAM available, we rarely think about optimizing our code. But sooner or later, we hit the limits a...