A single model for shaping, creating, accessing, storing data within a Database

Overview

'db' within pydantic - A single model for shaping, creating, accessing, storing data within a Database

Documentation Status PyPI versionUnit & Integration Tests

Key Features

  • Integrated Redis Caching Support
  • Automatic Migration on Schema Changes
  • Flexible Data Types
  • One Model for type validation & database access

Documentation

https://pydbantic.readthedocs.io/en/latest/

Setup

$ pip install pydbantic
$ pip install pydbantic[sqlite]
$ pip install pydbantic[mysql]
$ pip install pydbantic[postgres]

Basic Usage - Model

from typing import List, Optional
from pydantic import BaseModel, Field
from pydbantic import DataBaseModel, PrimaryKey

class Department(DataBaseModel):
    id: str = PrimaryKey()
    name: str
    company: str
    is_sensitive: bool = False

class Positions(DataBaseModel):
    id: str = PrimaryKey()
    name: str
    department: Department

class EmployeeInfo(DataBaseModel):
    ssn: str = PrimaryKey()
    first_name: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]

class Employee(DataBaseModel):
    id: str = PrimaryKey()
    employee_info: EmployeeInfo
    position: Positions
    salary: float
    is_employed: bool
    date_employed: Optional[str]

Basic Usage - Connecting a Database to Models

import asyncio
from pydbantic import Database
from models import Employee

async def main():
    db = await Database.create(
        'sqlite:///test.db',
        tables=[Employee]
    )

if __name__ == '__main__':
    asyncio.run(main())

Model Usage

from models import (
    Employee, 
    EmployeeInfo, 
    Position, 
    Department
)

async def main():
    # db creation is above

    # create department 
    hr_department = Department(
        id='d1234',
        name='hr'
        company='abc-company',
        is_sensitive=True,
    )

    # create a Position in Hr Department
    hr_manager = Position(
        id='p1234',
        name='manager',
        department=hr_department
    )
    
    # create information on an hr employee
    hr_emp_info = EmployeeInfo(
        ssn='123-456-789',
        first_name='john',
        last_name='doe',
        address='123 lane',
        city='snake city',
        zip=12345
    )

    # create an hr employee 
    hr_employee = Employee(
        id='e1234',
        employee_info=hr_emp_info,
        position=hr_manager,
        is_employed=True,
        date_employed='1970-01-01'
    )

Note: At this point only the models have been created, but nothing is saved in the database yet.

    # save to database
    await hr_employee.save()

Filtering

    # get all hr managers currently employed
    managers = await Employee.filter(
        position=hr_manager,
        is_employed=True
    )

Deleting

    # remove all managers not employed anymore
    for manager in await Employee.filter(
        position=hr_manager,
        is_employed=False
    ):
        await manager.delete()

Updating

    # raise salary of all managers
    for manager in await Employee.filter(
        position=hr_manager,
        is_employed=False
    ):
        manager.salary = manager.salary + 1000.0
        await manager.update() # or manager.save()

Save results in a new row created in Employee table as well as the related EmployeeInfo, Position, Department tables if non-existing.

What is pydbantic

pydbantic was built to solve some of the most common pain developers may face working with databases.

  • migrations
  • model creation / managment
  • caching

pydbantic believes that related data should be stored together, in the shape the developer plans to use

pydbantic knows data is rarely flat or follows a set schema

pydbantic understand migrations are not fun, and does them for you

pydbantic speaks many types

Pillars

Models

pydbantic most basic object is a DataBaseModel. This object may be comprised of almost any pickle-able python object, though you are encouraged to stay within the type-validation land by using pydantic's BaseModels and validators.

Primary Keys

DataBaseModel 's also have a priamry key, which is the first item defined in a model or marked with = PrimaryKey()

class NotesBm(DataBaseModel):
    id: str = PrimaryKey()
    text: Optional[str]  # optional
    data: DataModel      # required 
    coridinates: tuple   # required
    items: list          # required
    nested: dict = {'nested': True} # Optional - w/ Default

Model Types & Typing

DataBaseModel items are capable of being multiple layers deep following pydantic model validation

  • Primary Key - First Item, must be unique
  • Required - items without default values are assumed required
  • Optional - marked explicitly with typing.Optional or with a default value
  • Union - Accepts Either specified input type Union[str|int]
  • List[item] - Lists of specified items

Input datatypes without a natural / built in serialization path are serialized using pickle and stored as bytes. More on this later.

Migrations

pydbantic handles migrations automatically in response to detected model changes: New Field, Removed Field, Modified Field, Renamed Field, Primary Key Changes

Renaming an exiting column

Speical consideration is needed when renaming a field in a DataBaseModel, extra metadata __renamed__ is needed to ensure existing data is migrated:

# field `first_name` is renamed to `first_names`

class EmployeeInfo(DataBaseModel):
    __renamed__= [{'old_name': 'first_name', 'new_name': 'first_names'}]
    ssn: str = PrimaryKey()
    first_names: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]

Cache

Adding cache with Redis is easy with pydbantic, and is complete with built in cache invalidation.

    db = await Database.create(
        'sqlite:///test.db',
        tables=[Employee],
        cache_enabled=True,
        redis_url="redis://localhost"
    )

Models with arrays of Foreign Objects

DataBaseModel models can support arrays of both BaseModels and other DataBaseModel. Just like single DataBaseModel references, data is stored in separate tables, and populated automatically when the child DataBaseModel is instantiated.

from uuid import uuid4
from datetime import datetime
from typing import List, Optional
from pydbantic import DataBaseModel, PrimaryKey


def time_now():
    return datetime.now().isoformat()
def get_uuid4():
    return str(uuid4())

class Coordinate(DataBaseModel):
    time: str = PrimaryKey(default=time_now)
    latitude: float
    longitude: float

class Journey(DataBaseModel):
    trip_id: str = PrimaryKey(default=get_uuid4)
    waypoints: List[Optional[Coordinate]]


Comments
  • StopAsyncIteration Exception

    StopAsyncIteration Exception

    Hello, when I execute the following example I get an StopAsyncIteration Exception with no Exception Message.

    from pydbantic import Database
    
    class Department(DataBaseModel):
        id: str = PrimaryKey()
        name: str
        company: str
        is_sensitive: bool = False
    
    db = await Database.create('sqlite:///test.db',tables=[Department])
    

    Traceback:

    StopAsyncIteration                        Traceback (most recent call last)
    ~\AppData\Local\Temp/ipykernel_19192/144563389.py in <module>
          7     is_sensitive: bool = False
          8 
    ----> 9 db = await Database.create('sqlite:///test.db',tables=[Department])
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in create(cls, DB_URL, tables, cache_enabled, redis_url, logger, debug, testing)
        492 
        493         async with new_db:
    --> 494             await new_db.compare_tables_and_migrate()
        495 
        496         return new_db
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in compare_tables_and_migrate(self)
        161 
        162         # checkout database for migrations
    --> 163         database_init = await DatabaseInit.get(database_url=self.DB_URL)
        164 
        165         if not database_init:
    
    ~\Documents\.venv\lib\site-packages\pydbantic\core.py in get(cls, **p_key)
        626             if k != cls.__metadata__.tables[cls.__name__]['primary_key']:
        627                 raise f"Expected primary key {primary_key}=<value>"
    --> 628         result = await cls.select('*', where={**p_key})
        629         return result[0] if result else None
        630 
    
    ~\Documents\.venv\lib\site-packages\pydbantic\core.py in select(cls, where, alias, limit, offset, *selection)
        431         database = cls.__metadata__.database
        432 
    --> 433         results = await database.fetch(sel, cls.__name__, values)
        434 
        435         for result in cls.normalize(results):
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in fetch(self, query, table_name, values)
        459         self.log.debug(f"running query: {query} with {values}")
        460 
    --> 461         async with self as conn:
        462             row = await conn.fetch_all(query=query)
        463 
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in __aenter__(self)
        509                 continue
        510             self.connection_map[conn_id]['last'] = time.time()
    --> 511             return await self.connection_map[conn_id]['conn'].asend(None)
        512 
        513         conn_id = str(uuid.uuid4())
    
    StopAsyncIteration: 
    

    Versions

    • Python version 3.9.6
    • Pydbantic version 0.0.15
    • Operating system Windows 10
    opened by Phil997 6
  • README Shows Incomplete Import Statement

    README Shows Incomplete Import Statement

    By my reading, it looks like this example shows an incomplete import statement. The statement does not import all of the models required to create the database. (I think :stuck_out_tongue_closed_eyes: )

    Present documentation

    import asyncio
    from pydbantic import Database
    from models import Employee ### <---- Missing additional model imports here...?
    
    async def main():
        db = await Database.create(
            'sqlite:///test.db',
            tables=[
                Employee,
                EmployeeInfo,
                Positions,
                Department
            ]
        )
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    Suggested Documentation

    import asyncio
    from pydbantic import Database
    from models import Employee, EmployeeInfo, Positions, Department
    
    async def main():
        db = await Database.create(
            'sqlite:///test.db',
            tables=[
                Employee,
                EmployeeInfo,
                Positions,
                Department
            ]
        )
    
    if __name__ == '__main__':
        asyncio.run(main())
    
    opened by engineerjoe440 4
  • bump databases version to 0.6.0 and pydantic to 1.9.1

    bump databases version to 0.6.0 and pydantic to 1.9.1

    It's not possible to use easyauth with the latest encoding/databases version 0.6.0 since pydbantic depends on version 0.5.3. Is it possible to bump the version or are there any backwards compatibility issues?

    In addition to databases consider upgrading pydantic to 1.9.1 - latest.

    opened by KKP4 3
  • Add a field with datetime.utcnow in documentation example

    Add a field with datetime.utcnow in documentation example

    Hi there,

    Working with dates can be quite challenging, for example when trying to add something like "creation_date" or "updated_at" in plain SQLalchemy we would do something like :

    from sqlalchemy.sql import func
    
    time_created = Column(DateTime(timezone=True), server_default=func.now())
    time_updated = Column(DateTime(timezone=True), onupdate=func.now())
    

    How would we be able to achieve the same thing in Pydbantic ?

    opened by sorasful 3
  • data and metadata tables advice

    data and metadata tables advice

    Thank you Josh for your work with pydbantic,

    I want to store both data and metadata for my data

    How do you recommend creating tables that can store pandas dataframes with arbitrary columns?

    How about metadata where the metadata has some JSON fields?

    question 
    opened by joamatab 2
  • How can we access one's parent through relationship ?

    How can we access one's parent through relationship ?

    Imagine we have

    # create department
        hr_department = Department(
            id='d1234',
            name='hr',
            company='abc-company',
                is_sensitive=True,
        )
    
    
        # create a Position in Hr Department
        hr_manager = Position(
            id='p1234',
            name='manager',
            department=hr_department
        )
    

    And suppose I want to do :

    manager = hr_manager.department.manager
    

    for example, would it be possible ? How could we setup this ?

    opened by sorasful 2
  • BUG - Filtering with order_by & limit / offset

    BUG - Filtering with order_by & limit / offset

    Description

    On version 0.0.21, filtering might fail if queries specify both order_by combined with limit and offset.

    Traceback

    (order_by=order, limit=limit, offset=offset)
      File "/usr/local/lib/python3.8/site-packages/pydbantic/core.py", line 1143, in all
        return await cls.select('*', **parameters, backward_refs=backward_refs)
      File "/usr/local/lib/python3.8/site-packages/pydbantic/core.py", line 966, in select
        sel = sel.order_by(order_by)
      File "<string>", line 2, in order_by
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/sql/base.py", line 110, in _generative
        x = fn(self, *args, **kw)
      File "<string>", line 2, in order_by
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 229, in generate
        assertion(self, fn.__name__)
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 312, in _no_limit_offset
        raise sa_exc.InvalidRequestError(
    sqlalchemy.exc.InvalidRequestError: Query.order_by() being called on a Query which already has LIMIT or OFFSET applied.  Call order_by() before limit() or offset() are applied.
    
    
    
    bug 
    opened by codemation 1
  • Find a way to generate automatically PrimaryKeys instead of coding them

    Find a way to generate automatically PrimaryKeys instead of coding them

    IMHO, It would be essential that PrimaryKey() generates automatically an id, it could even depend on the type we are passing, for example :

    class Employee(DataBaseModel):
        id: str = PrimaryKey()  # could use uuid.uuid4()
        id: int = PrimaryKey()  # could use random.randint(0, 9e99) (just an example, would need something better)
    
    

    A possibility would also to set PrimaryKey as Optional, what do you think ?

    opened by sorasful 1
  • Chore: Simplify logical expression using `De Morgan identities`  ✨

    Chore: Simplify logical expression using `De Morgan identities` ✨

    When reading logical statements it is important for them to be as simple as possible, that's why it's better to stick in one code way to let it more easy and fast.

    opened by yezz123 1
  • Alembic migrations and mod many methods

    Alembic migrations and mod many methods

    Whats Inside

    • Added insert_many, delete_many methods to DatabaseModel
    • Added support for alembic migrations
    • Improved automatic migrations with alembic
    opened by codemation 0
  • Add unique model constraint

    Add unique model constraint

    Description

    This PR adds the ability to define Unique() constraint on a DataBaseModel field, allowing additional unique constraint protection alongside PrimaryKey.

    Example Model

    from uuid import uuid4
    from datetime import datetime
    from typing import List, Optional, Union
    from pydbantic import DataBaseModel, PrimaryKey, Unique
    
    def uuid_str():
        return str(uuid4())
    
    class Department(DataBaseModel):
        department_id: str = PrimaryKey()
        name: str = Unique()
        company: str
        is_sensitive: bool = False
        positions: List[Optional['Positions']] = []
    
    class Positions(DataBaseModel):
        position_id: str = PrimaryKey()
        name: str = Unique()
        department: Department = None
        employees: List[Optional['Employee']] = []
    
    class EmployeeInfo(DataBaseModel):
        ssn: str = PrimaryKey()
        bio_id: str = Unique(default=uuid_str)
        first_name: str
        last_name: str
        address: str
        address2: Optional[str]
        city: Optional[str]
        zip: Optional[int]
        new: Optional[str]
        employee: Optional[Union['Employee', dict]] = None
    
    opened by codemation 0
Releases(0.0.27)
  • 0.0.27(Dec 13, 2022)

  • 0.0.26(Dec 13, 2022)

    What's Changed

    • Alembic migrations and mod many methods by @codemation in https://github.com/codemation/pydbantic/pull/37

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.25...0.0.26

    Source code(tar.gz)
    Source code(zip)
  • 0.0.25(Sep 8, 2022)

    What's Changed

    • sync pydnatic version in setup.py with requirements.txt (1.9.1) by @eudoxos in https://github.com/codemation/pydbantic/pull/36

    New Contributors

    • @eudoxos made their first contribution in https://github.com/codemation/pydbantic/pull/36

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.24...0.0.25

    Source code(tar.gz)
    Source code(zip)
  • 0.0.24(Aug 17, 2022)

    What's Changed

    • updated pydantic / databases pinned versions by @codemation in https://github.com/codemation/pydbantic/pull/35

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.23...0.0.24

    Source code(tar.gz)
    Source code(zip)
  • 0.0.23(Aug 16, 2022)

    What's Changed

    • Add unique model constraint by @codemation in https://github.com/codemation/pydbantic/pull/34

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.22...0.0.23

    Source code(tar.gz)
    Source code(zip)
  • 0.0.22(Jan 31, 2022)

  • 0.0.21(Jan 25, 2022)

    What's Changed

    • Migration Enhancements by @codemation in https://github.com/codemation/pydbantic/pull/30

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.20...0.0.21

    Source code(tar.gz)
    Source code(zip)
  • 0.0.20(Jan 24, 2022)

    What's Changed

    • added missing import statments for example by @engineerjoe440 in https://github.com/codemation/pydbantic/pull/28
    • Improved pickle loads error handling by @codemation in https://github.com/codemation/pydbantic/pull/29

    New Contributors

    • @engineerjoe440 made their first contribution in https://github.com/codemation/pydbantic/pull/28

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.19...0.0.20

    Source code(tar.gz)
    Source code(zip)
  • 0.0.19(Jan 13, 2022)

    What's Changed

    • Dynamic model relationships by @codemation in https://github.com/codemation/pydbantic/pull/25

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.18...0.0.19

    Source code(tar.gz)
    Source code(zip)
  • 0.0.18(Dec 6, 2021)

    What's Changed

    • Improved model filtering conditions by @codemation in https://github.com/codemation/pydbantic/pull/22

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.17...0.0.18

    Source code(tar.gz)
    Source code(zip)
  • 0.0.17(Dec 4, 2021)

    What's Changed

    • Feature - DataBaseModel Count and filter count by @codemation in https://github.com/codemation/pydbantic/pull/21

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.16...0.0.17

    Source code(tar.gz)
    Source code(zip)
  • 0.0.16(Dec 2, 2021)

    What's Changed

    • Object Filtering - order_by by @codemation in https://github.com/codemation/pydbantic/pull/20

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.15...0.0.16

    Source code(tar.gz)
    Source code(zip)
  • 0.0.15(Dec 2, 2021)

    What's Changed

    • Filter operators and pagination by @codemation in https://github.com/codemation/pydbantic/pull/17

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.14...0.0.15

    Source code(tar.gz)
    Source code(zip)
  • 0.0.14(Nov 23, 2021)

    What's Changed

    • Fix/Enhancement - Improved db connection context manager by @codemation in https://github.com/codemation/pydbantic/pull/15

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.13...0.0.14

    Source code(tar.gz)
    Source code(zip)
  • 0.0.13(Nov 22, 2021)

    Whats changed?

    Multi App same DB support & Improvement Migrations - replaced complete TableMeta lookups during compare_tables_and_migrate with tables in self.tables, thus allowing the same database to be used accross multiple applications that do not share model definitions.

    Source code(tar.gz)
    Source code(zip)
  • 0.0.12(Nov 17, 2021)

  • 0.0.11(Nov 17, 2021)

    • Improved support for foreign model updates, .save() will invoke creation of foreign objects if not existing,
    • Added support for Arrays of Foreign References where a single field in a DataBaseModel may contain a list of other DataBaseModel objects
    from uuid import uuid4
    from datetime import datetime
    from typing import List, Optional
    from pydbantic import DataBaseModel, PrimaryKey
    
    
    def time_now():
        return datetime.now().isoformat()
    def get_uuid4():
        return str(uuid4())
    
    class Coordinate(DataBaseModel):
        time: str = PrimaryKey(default=time_now)
        latitude: float
        longitude: float
    
    class Journey(DataBaseModel):
        trip_id: str = PrimaryKey(default=get_uuid4)
        waypoints: List[Optional[Coordinate]]
    
    
    Source code(tar.gz)
    Source code(zip)
Owner
Joshua Jamison
Joshua Jamison
Easy-to-use data handling for SQL data stores with support for implicit table creation, bulk loading, and transactions.

dataset: databases for lazy people In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files. Read the

Friedrich Lindenberg 4.2k Dec 26, 2022
MongoEngine flask extension with WTF model forms support

Flask-MongoEngine Info: MongoEngine for Flask web applications. Repository: https://github.com/MongoEngine/flask-mongoengine About Flask-MongoEngine i

MongoEngine 815 Jan 3, 2023
Pydantic model support for Django ORM

Pydantic model support for Django ORM

Jordan Eremieff 318 Jan 3, 2023
Python 3.6+ Asyncio PostgreSQL query builder and model

windyquery - A non-blocking Python PostgreSQL query builder Windyquery is a non-blocking PostgreSQL query builder with Asyncio. Installation $ pip ins

null 67 Sep 1, 2022
Rich Python data types for Redis

Created by Stephen McDonald Introduction HOT Redis is a wrapper library for the redis-py client. Rather than calling the Redis commands directly from

Stephen McDonald 281 Nov 10, 2022
Galvanalyser is a system for automatically storing data generated by battery cycling machines in a database

Galvanalyser is a system for automatically storing data generated by battery cycling machines in a database, using a set of "harvesters", whose job it

Battery Intelligence Lab 20 Sep 28, 2022
A tool for creating credentials for accessing S3 buckets

s3-credentials A tool for creating credentials for accessing S3 buckets For project background, see s3-credentials: a tool for creating credentials fo

Simon Willison 138 Jan 6, 2023
aiopg is a library for accessing a PostgreSQL database from the asyncio

aiopg aiopg is a library for accessing a PostgreSQL database from the asyncio (PEP-3156/tulip) framework. It wraps asynchronous features of the Psycop

aio-libs 1.3k Jan 3, 2023
aiomysql is a library for accessing a MySQL database from the asyncio

aiomysql aiomysql is a "driver" for accessing a MySQL database from the asyncio (PEP-3156/tulip) framework. It depends on and reuses most parts of PyM

aio-libs 1.5k Jan 3, 2023
Python CLI for accessing CSCI320 PDM Database

p320_14 Python CLI for accessing CSCI320 PDM Database Authors: Aidan Mellin Dan Skigen Jacob Auger Kyle Baptiste Before running the application for th

Aidan Mellin 1 Nov 23, 2021
Setup a flask project using a single command, right from creating virtual environment to creating Procfile for deployment.

AutoFlask-Setup About AutoFlask-Setup can help you set up a new Flask Project, right from creating virtual environment to creating Procfile for deploy

Ashutosh Krishna 1 Oct 21, 2021
A reusable Django model field for storing ad-hoc JSON data

jsonfield jsonfield is a reusable model field that allows you to store validated JSON, automatically handling serialization to and from the database.

Ryan P Kilby 1.1k Jan 3, 2023
Using SQLite within Python to create database and analyze Starcraft 2 units data (Pandas also used)

SQLite python Starcraft 2 English This project shows the usage of SQLite with python. To create, modify and communicate with the SQLite database from

null 1 Dec 30, 2021
Convert your JSON data to a valid Python object to allow accessing keys with the member access operator(.)

JSONObjectMapper Allows you to transform JSON data into an object whose members can be queried using the member access operator. Unlike json.dumps in

Owen Trump 4 Jul 20, 2022
Lightweight library for accessing data and configuration

accsr This lightweight library contains utilities for managing, loading, uploading, opening and generally wrangling data and configurations. It was ba

appliedAI Initiative 7 Mar 9, 2022
A part of HyRiver software stack for accessing hydrology data through web services

Package Description Status PyNHD Navigate and subset NHDPlus (MR and HR) using web services Py3DEP Access topographic data through National Map's 3DEP

Taher Chegini 51 Dec 10, 2022
Historic weather - Home Assistant custom component for accessing historic weather data

Historic Weather for Home Assistant (CC) 2022 by Andreas Frisch github@fraxinas.

Andreas Frisch 1 Jan 10, 2022
The Timescale NFT Starter Kit is a step-by-step guide to get up and running with collecting, storing, analyzing and visualizing NFT data from OpenSea, using PostgreSQL and TimescaleDB.

Timescale NFT Starter Kit The Timescale NFT Starter Kit is a step-by-step guide to get up and running with collecting, storing, analyzing and visualiz

Timescale 102 Dec 24, 2022
Microsoft Azure provides a wide number of services for managing and storing data

Microsoft Azure provides a wide number of services for managing and storing data. One product is Microsoft Azure SQL. Which gives us the capability to create and manage instances of SQL Servers hosted in the cloud. This project, demonstrates how to use these services to manage data we collect from different sources.

Riya Vijay Vishwakarma 1 Dec 12, 2021