Skip to main content

쉬운 API 생성 및 프로젝트 관리를 위한 프레임워크

Project description

This README is the English version.
Click here for the Korean version.

Contributors Forks Stars License PyPI


Table of Contents




How to Contribute

Anyone can freely contribute to Ezy API!
Please follow the procedure below to participate comfortably.

Fork the Project

Fork the project to work in your personal repository and then submit a PR.

Create a Branch

When starting new work, please create a branch. Branches are typically written in the form of feature/{feature-name}.

# Move to the main branch and sync with the latest state
$ git checkout main
$ git pull origin main

# Create and move to a new branch
$ git checkout -b feature/feature-name

Branch Naming Convention

Please write branch names according to the nature of your work.

Type Description Example
feature/ New feature development feature/login-api, feature/add-user-api
fix/ Bug fixes fix/login-bug, fix/routing-error
docs/ Documentation work (README, comments, etc.) docs/update-readme, docs/api-docs
refactor/ Code refactoring refactor/login-service, refactor/db-helper
test/ Add or modify test code test/user-service-test
chore/ Build settings, package management, other miscellaneous tasks chore/update-deps, chore/ci-config

Tip

Branch names should clearly reflect the content of your work.

Work and Commit

After completing your work, please write commit messages according to the commit conventions below.

Commit Message Convention

Tag Description
feat Add new features
fix Fix bugs
docs Modify documentation (README, comments, etc.)
style Code formatting, spelling fixes, etc. that don't affect the code functionality
refactor Code refactoring (improving internal logic without changing behavior)
test Add or modify test code
chore Build tasks, package manager configuration, maintenance work, etc.

Commit Examples

$ git commit -m "feat: Add user API"
$ git commit -m "fix: Fix incorrect router path"
$ git commit -m "docs: Add installation method to README"

Push to Remote Repository

Push your branch to the remote repository.

$ git push origin feature/feature-name

Submit a Pull Request (PR)

  • Create a Pull Request on GitHub.
  • Briefly explain what work you did in the PR description.
  • Proceed with code review with team members.

Merge

  • After review and approval, merge to the main branch.
  • After merging, always sync your main branch to the latest before starting other work.
$ git checkout main
$ git pull origin main



First Steps

Background

We like Nest.js, but we thought that Nest.js's Controller, Module, etc. were unnecessary for simple tasks.

Getting Started

In this document, we'll explore the core principles of Ezy API. To understand the essential components of an Ezy API application, you need to build a basic CRUD application covering many areas at a fundamental level.

Language

Ezy API uses the Python language.

In the future, we plan to support other languages such as TypeScript and Java.

Prerequisites

Make sure Python (>= 3.11) is installed on your operating system.

Setup

Setting up a new project with Ezy API CLI is very simple. If you have pip installed, you can create a new Ezy API project using the following command in your OS terminal:

$ pip install ezyapi
$ ezy new project-name

A project-name directory will be created, along with main.py and CLI configuration files.

The basic structure of the project is as follows:

app_service.py
ezy.json
main.py

Tip

The above files can be found here.



The core files described above can be briefly explained as follows:

Filename Description
app_service.py Basic service file
ezy.json CLI command configuration file
main.py Entry file. Uses the core function EzyAPI to create an Ezy API application instance.

If you don't understand the services described above, that's okay. More detailed explanations will come in later chapters!



Let's start by creating a simple main.py file. This file contains the main module that starts the application.

# main.py
from ezyapi import EzyAPI
from ezyapi.database import DatabaseConfig
from user.user_service import UserService
from app_service import AppService

if __name__ == "__main__":
    app = EzyAPI()
    app.run(port=8000)

Running the Application

You can run the application using the following command in your OS terminal:

$ ezy run start



Services

What is a Service?

In Ezy API, a Service is a core component that handles requests and performs business logic.
It plays a similar role to Controllers or Services in Nest.js, but Ezy API is designed to allow you to configure APIs with just services in a more concise and intuitive way.

Service Structure

A service is created by inheriting from the EzyService class.
Below is an example of a basic service:

Tip

You can create a service using $ ezy g res user

# app_service.py
from ezyapi import EzyService

class AppService(EzyService):
    async def get_app(self) -> str:
        return "Hello, World!"
  • By inheriting from EzyService, you can define API endpoints as asynchronous functions within the service.
  • The function name becomes the API endpoint URL.
    • For example, a function called get_user is automatically mapped to the /user/ path with the GET method.
      • As an exception, if the service name is app, it's mapped to the root path.
  • Functions can be defined with async to enable asynchronous processing.

URL Mapping Rules

The function names of the service are automatically mapped to URL endpoints.

Function Name HTTP Method URL
get_user GET /user/
list_users GET /user/
create_user POST /user/
update_user PUT /user/
delete_user DELETE /user/
edit_user PATCH /user/

Tip

The get, update, delete, edit methods can use by_id etc. to use path parameters Example: get_user_by_id ➡️ GET /user/{id}

Service Registration

Services can be registered to the EzyAPI instance in main.py.

# main.py
from ezyapi import EzyAPI
from ezyapi.database import DatabaseConfig
from app_service import AppService

if __name__ == "__main__":
    app.add_service(AppService)
    app.run(port=8000)

Path Parameter Example

Ezy API automatically maps parameters to URL paths when you add by_id, by_name, etc. to the function name.

# user_service.py
from ezyapi import EzyService

class UserService(EzyService):
    async def get_user_by_id(self, id: int) -> dict:
        return {"id": id, "name": "John Doe"}
  • get_user_by_id ➡️ Automatically mapped to the GET /user/{id} path.
  • id is used as a path parameter in the URL path.

Request Example

GET /user/10

Response Example

{
  "id": 10,
  "name": "John Doe"
}

Query Parameter Example

Query parameters can be received as query strings by defining Optional and default values in function arguments.

# user_service.py
from ezyapi import EzyService
from typing import Optional, List

class UserService(EzyService):
    async def list_users(self, name: Optional[str] = None, age: Optional[int] = None) -> List[dict]:
        filters = {}
        if name:
            filters["name"] = name
        if age:
            filters["age"] = age

        return [{"id": 1, "name": name or "John", "age": age or 25}]
  • list_users ➡️ GET /user/
  • You can pass name and age as query strings.

Request Example

GET /user/?name=Alice&age=30

Response Example

[
  {
    "id": 1,
    "name": "Alice",
    "age": 30
  }
]

Decorator Example (@route)

You can manually specify the URL and method by using the @route() decorator directly on service functions.

# user_service.py
from ezyapi import EzyService, route

class UserService(EzyService):
    @route('get', '/name/{name}', description="Get user by name")
    async def get_user_by_name(self, name: str) -> dict:
        return {"name": name, "email": "example@example.com"}
  • @route('get', '/name/{name}') ➡️ Sets the path to GET /name/{name}.
  • description is used for API documentation.

Request Example

GET /name/Alice

Response Example

{
  "name": "Alice",
  "email": "example@example.com"
}

Tip

Using the @route() decorator allows you to override automatic mapping and freely set the desired URL and HTTP method.




Database and Entities

Database Configuration

Ezy API supports multiple database types including SQLite, MySQL, PostgreSQL, and MongoDB.

# main.py
from ezyapi import EzyAPI
from ezyapi.database import DatabaseConfig
from user.user_service import UserService

if __name__ == "__main__":
    app = EzyAPI()
    
    # SQLite configuration
    db_config = DatabaseConfig(
        type="sqlite",
        path="./app.db"
    )
    
    # Or MySQL configuration
    # db_config = DatabaseConfig(
    #     type="mysql",
    #     host="localhost",
    #     port=3306,
    #     username="root",
    #     password="password",
    #     database="myapp"
    # )
    
    app.add_database(db_config)
    app.add_service(UserService)
    app.run(port=8000)

Entity Definition

Entities are defined by inheriting from EzyEntityBase. You can use TypeORM-style annotations for advanced column configuration.

Basic Entity (Simple Approach)

# user/entity/user_entity.py
from ezyapi import EzyEntityBase

class UserEntity(EzyEntityBase):
    def __init__(self, name: str = "", email: str = ""):
        self.name = name
        self.email = email
    
    # Default: id becomes auto-increment primary key
    id: int = None
    name: str = ""
    email: str = ""

Advanced Entity with Annotations

For more control over database columns, you can use TypeORM-style annotations:

# user/entity/user_entity.py
from ezyapi import EzyEntityBase, PrimaryColumn, PrimaryGeneratedColumn, Column
from typing import Annotated, Optional

class UserEntity(EzyEntityBase):
    def __init__(self, id: int = 0, name: str = "", major: Optional[str] = None, 
                 grade: Optional[int] = None, isTeacher: bool = None):
        self.id = id
        self.name = name
        self.major = major
        self.grade = grade
        self.isTeacher = isTeacher
    
    # Custom primary key with specific column type
    id: Annotated[int, PrimaryColumn(column_type="INT")] = 0
    
    # Optional: Add annotations only for fields that need special configuration
    name: Annotated[str, Column(nullable=False, column_type="VARCHAR(100)")] = ""
    major: Optional[str] = None
    grade: Optional[int] = None
    isTeacher: bool = None

Column Annotation Types

Annotation Description Example
PrimaryColumn() Custom primary key (user-provided value) id: Annotated[str, PrimaryColumn(column_type="VARCHAR(50)")] = None
PrimaryGeneratedColumn() Auto-increment primary key id: Annotated[int, PrimaryGeneratedColumn(column_type="BIGINT")] = None
Column() Regular column with options name: Annotated[str, Column(nullable=False, unique=True)] = ""

Column Options

  • column_type: Specify database column type (e.g., "VARCHAR(100)", "TEXT", "BIGINT")
  • nullable: Whether the column can be NULL (default: True)
  • unique: Whether the column should have a unique constraint (default: False)
  • auto_increment: Whether the column should auto-increment (default: False for Column, True for PrimaryGeneratedColumn)

Multiple Primary Key Examples

# String primary key
class ProductEntity(EzyEntityBase):
    product_code: Annotated[str, PrimaryColumn(column_type="VARCHAR(20)")] = None
    name: str = ""
    price: float = 0.0

# Custom auto-increment primary key
class OrderEntity(EzyEntityBase):
    order_id: Annotated[int, PrimaryGeneratedColumn(column_type="BIGINT")] = None
    user_id: int = None
    total_amount: float = 0.0

# UUID primary key
class SessionEntity(EzyEntityBase):
    session_token: Annotated[str, PrimaryColumn(column_type="VARCHAR(36)")] = None
    user_id: int = None
    expires_at: str = None

Note

  • Annotations are optional - you only need to add them for fields requiring special database configuration
  • Fields without annotations use default behavior (regular columns)
  • The id: int = None field automatically becomes an auto-increment primary key if no other primary key is specified

Entity Relationships

Ezy API supports TypeORM-style entity relationships including OneToMany and ManyToOne. You can define relationships between entities and load related data efficiently.

Defining Relationships

# user/entity/user_entity.py
from ezyapi import EzyEntityBase, OneToMany, ManyToOne
from typing import List, Optional

class UserEntity(EzyEntityBase):
    def __init__(self, name: str = "", email: str = ""):
        self.name = name
        self.email = email
    
    id: int = None
    name: str = ""
    email: str = ""
    
    # OneToMany relationship: A user can have multiple posts
    posts: List['PostEntity'] = OneToMany('PostEntity', 'user_id')

class PostEntity(EzyEntityBase):
    def __init__(self, title: str = "", content: str = "", user_id: int = None):
        self.title = title
        self.content = content
        self.user_id = user_id
    
    id: int = None
    title: str = ""
    content: str = ""
    user_id: int = None
    
    # ManyToOne relationship: Multiple posts can belong to one user
    user: UserEntity = ManyToOne(UserEntity, 'user_id')

Relationship Types

Relationship Description Example
OneToMany(target_entity, mapped_by) One entity has many related entities posts: List['PostEntity'] = OneToMany('PostEntity', 'user_id')
ManyToOne(target_entity, foreign_key) Many entities belong to one entity user: UserEntity = ManyToOne(UserEntity, 'user_id')

Loading Related Data

Use the relations parameter in repository methods to load related entities:

# user_service.py
from ezyapi import EzyService
from ezyapi.database import DatabaseConfig
from user.entity.user_entity import UserEntity

class UserService(EzyService):
    def __init__(self):
        db_config = DatabaseConfig.get_instance()
        self.user_repository = db_config.get_repository(UserEntity)
    
    async def get_users_with_posts(self):
        # Load users with their posts
        users = await self.user_repository.find(relations=["posts"])
        return users
    
    async def get_user_with_posts_by_id(self, user_id: int):
        # Load a specific user with their posts
        user = await self.user_repository.find_one(
            where={"id": user_id}, 
            relations=["posts"]
        )
        return user

# post_service.py
from post.entity.post_entity import PostEntity

class PostService(EzyService):
    def __init__(self):
        db_config = DatabaseConfig.get_instance()
        self.post_repository = db_config.get_repository(PostEntity)
    
    async def get_posts_with_users(self):
        # Load posts with their associated users
        posts = await self.post_repository.find(relations=["user"])
        return posts

Relationship Loading Examples

# Load multiple relationships
users = await user_repository.find(relations=["posts", "profile", "comments"])

# Load specific user with relationships
user = await user_repository.find_one(
    where={"id": 1}, 
    relations=["posts"]
)

# The loaded user object will have the posts attribute populated
print(user.posts)  # List of PostEntity objects



CLI Overview

Ezy CLI is a command-line interface tool designed to simplify Ezy API project management. It provides various commands to easily perform project creation, building, testing, and execution.

Installation

Ezy CLI can be installed using pip:

$ pip install ezyapi

Note

Make sure Python 3.11 or higher is installed on your system.

Commands

Ezy CLI supports the following commands:

Command Description
new <project_name> Creates a new Ezy API project with the specified name.
generate <type> <name> or g <type> <name> Generates a component of the specified type (e.g., 'res' for resource) and name.
install [packages...] or install -r <requirements.txt> Installs dependencies in the ezy_modules directory. You can specify packages directly or use a requirements.txt file.
run <script> Runs scripts defined in ezy.json (e.g., 'dev' or 'start').
test Runs tests in the 'test' directory using pytest.
lint Checks code style using flake8.
info Displays CLI version, Python version, platform, and current directory information.
init [project_name] Initializes a new Ezy project in the current directory. If no project name is specified, the current directory name is used.

Examples

Create a New Project

$ ezy new my_project

Creates the basic structure of an Ezy API project in a directory named my_project.

Generate a Resource

$ ezy generate res user

Creates a resource named "user", optionally including CRUD operations.

Install Dependencies

To install dependencies listed in ezy.json:

$ ezy install

To install specific packages:

$ ezy install requests numpy

To install from a requirements.txt file:

$ ezy install -r requirements.txt

Run Scripts

Assuming scripts are defined in ezy.json, for example:

{
  "scripts": {
    "start": "python3 main.py",
    "dev": "python3 main.py --dev"
  }
}

You can execute them as follows:

$ ezy run start

Run Tests

To run tests:

$ ezy test

Note

pytest must be installed.

Lint Code

To check for code style issues:

$ ezy lint

Note

flake8 must be installed.

View Information

To display CLI and system information:

$ ezy info

Initialize a Project

To initialize a new Ezy project in the current directory:

$ ezy init

Project Structure

When you create a new project with the ezy new <project_name> command, the following structure is created:

project_name/
├── ezy.json
├── main.py
├── app_service.py
├── test/
│   └── (test files)
├── ezy_modules/
│   └── (installed dependencies)
└── .gitignore
  • ezy.json: Project settings (including dependencies and scripts).
  • main.py: Application entry point.
  • app_service.py: Example service.
  • test/: Directory for test files.
  • ezy_modules/: Directory for project-specific dependencies.
  • .gitignore: Git ignore file.

When you generate a resource with the ezy generate res <name> command, the following structure is created:

<name>/
├── __init__.py
├── dto/
│   ├── __init__.py
│   ├── <name>_create_dto.py
│   └── <name>_update_dto.py
├── entity/
│   ├── __init__.py
│   └── <name>_entity.py
└── <name>_service.py

A test file is also created at test/test_<name>_service.py.

Note

  • The CLI uses color codes to enhance readability in terminals that support ANSI colors.
  • The test command requires pytest to be installed. It is included in the default dependencies of new projects.
  • The lint command requires flake8. You may need to install it separately.
  • The update command currently only simulates updates without actually performing them.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ezyapi-1.7.0.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ezyapi-1.7.0-py3-none-any.whl (73.5 kB view details)

Uploaded Python 3

File details

Details for the file ezyapi-1.7.0.tar.gz.

File metadata

  • Download URL: ezyapi-1.7.0.tar.gz
  • Upload date:
  • Size: 45.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for ezyapi-1.7.0.tar.gz
Algorithm Hash digest
SHA256 d5b1298be521eb5dfa65b879b4502b448c4f9563028c7a153bed3dddf352dddb
MD5 d071c69b8f239327e99a90da7d58adbb
BLAKE2b-256 03db2e7fb671ce1be18ffc9238ab5d4492f0862e400c7918475188fe7a570770

See more details on using hashes here.

File details

Details for the file ezyapi-1.7.0-py3-none-any.whl.

File metadata

  • Download URL: ezyapi-1.7.0-py3-none-any.whl
  • Upload date:
  • Size: 73.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for ezyapi-1.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 923e7556bd7cb624586b99388a4bf554956491539d0eb46947062840db4a5fd8
MD5 f12c4b319abb24143032084fb5988fbc
BLAKE2b-256 5b4fa15738becece3ca691719acaab1c0f42b130d0184aa59b3891f8aecb2aef

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page