orbro-python-sdk


Nameorbro-python-sdk JSON
Version 0.19 PyPI version JSON
download
home_pagehttps://www.notion.so/ORBRO-Connect-7065dd4435264beebdeb52c7f7408820
SummaryORBRO-Connect SDK for Python(FastAPI/Flask)
upload_time2023-02-06 05:42:48
maintainer
docs_urlNone
authorORBRO
requires_python>=3.7
licenseApache License 2.0
keywords orbro connect iot digitaltwin rtls
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # README #
 [ORBRO Platform](https://orbro.io)는 앱을 개발하기 위한 다양한 API를 제공하며, 위젯과 앱 형태로 ORBRO Platform 상에 하나의 통합된 UI로 배포할 수 있습니다. 배포된 앱들은 ORBRO Platform을 사용하는 모든 기업/기관/사용자에게 공개하거나 본인이 소속한 조직에서만 활용할 수도 있습니다.  

본 파이썬 SDK는 FastAPI와 Flask 등의 주요 Python Web Framework에서 ORBRO Platform의 Connect 앱 개발을 위해 플랫폼 인증 관리를 편하게 해주고 플랫폼 API의 호출을 가능하게 합니다. 

### Requirements

---
Python 3.7+

### Installation

[PyPI][pypi] 을 통한 설치를 권장합니다.

```bash
$ pip install orbro-python-sdk
```

### 시작하기

---
####  SDK가 제공하는 기능
**ORBRO Connect Python SDK**는 아래와 같은 기능들이 내장되어있거나 지원합니다.

- `orbro_sdk`: FastAPI/Flask 등의 어플리케이션으로 초기화되는 OrbroConnect class '제공'
- `orbro_sdk.datastore`: SQLAlchemy/SQL 기반의 Client 정보 저장소, Dict/Redis 기반의 Access Token 저장소 '제공' 
- `orbro_sdk.models`: Connect 앱 토큰, Lifecycle에 따른 설치/삭제 Request, Client 정보 Mixin 모델 '제공'
- `orbro_sdk.fastapi.middleware`: FastAPI용. Web/Mobile 등이 SDK를 사용한 Connect 앱(BE)에 요청(Request)할 때의 인증 전처리 미들웨어 '내장'
- `orbro_sdk.fastapi.api_router`: FastAPI용. App Descriptor에 명시된 lifecycle.installed, lifecycle.uninstalled URL에 따른 ORBRO 플랫폼 Webhook 호출 요청(Request) 및 Client 정보에 대한 처리 '내장'
- `orbro_sdk.fastapi.dependencies`: FastAPI용. DI로 사용하기위한 인증, 저장소, API 호출이 가능한 AsyncWebClient의 Dependencies '제공'

####  App Descriptor
connect.json과 같은 JSON 형식의 앱 Descriptor 파일 작성을 통해 ORBRO Platform에 Connect 앱을 배포할 수 있습니다.
SDK 기본적으로 ORBRO Platform에서 발급된  Client ID와 Client Seceret과 하단의 앱 Descriptor 파일을 기반으로 동작합니다.
더 상세한 내용은 [Developer Guide - ORBRO Connect](https://orbro.notion.site/ORBRO-Connect-7065dd4435264beebdeb52c7f7408820)에서 확인할 수 있습니다.

```json
{
  "id": "com.kongtech.sdk.tester",
  "name": "SDK Test App.",
  "description": "로컬 및 개발 서버에서의 SDK 테스트",
  "vendor": {
    "name": "Kongtech",
    "url": "https://home.orbro.io"
  },
  "baseUrl": "https://f5cd-210-97-92-56.ngrok.io",
  "lifecycle": {
    "installed": "/installed",
    "uninstalled": "/uninstalled",
    "enabled": "/enabled",
    "disabled": "/disabled"
  },
  "scopes": [
    "every"
  ],
  "modules": {
    "webhooks": [
      {
        "event_name": "device:device_updated",
        "callback_api": "https://f5cd-210-97-92-56.ngrok.io/webhook/device",
        "property": "profile_id",
        "value": "Fixed.UWB.Anchor"
      },
      {
        "event_name": "device:device_updated",
        "callback_api": "https://f5cd-210-97-92-56.ngrok.io/webhook/device",
        "property": "profile_id",
        "value": "Movable.IndoorCamera.RTLS"
      }
    ]
  },
  "version": "0.1"
}
```

####  클라이언트 정보 모델 정의
ORBRO Platform에서 앱 설치/삭제 시에 lifecycle webhook으로 전달되는 정보를 기반으로 앱을 설치한 클라이언트 정보가 관리됩니다.

```python
from orbro_sdk.models import ConnectAppClientInfoMixin
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

# SQL Model
class ClientInfo(Base, ConnectAppClientInfoMixin):
    pass

'''
    class ConnectAppClientInfoMixin:
        """Connect App Shared Secret Info"""
        __tablename__ = 'connect_app_client_info'
        id = Column(Integer, primary_key=True)
        organization_id = Column(String(255), nullable=False)
        shared_secret = Column(String(255), nullable=False)
        subdomain = Column(String(255))
        installed_user_id = Column(String(255))
        created_time = Column(DateTime, default=func.now())
        updated_time = Column(DateTime, default=func.now())
'''
```
####  개발(DEV) 환경에서 SDK 초기화하기
SDK는 로컬(Local) 또는 개발(DEV) 환경에서 클라이언트 정보 저장소를 sqlite로 토큰 정보 저장소는 Dictionary로 자동으로 초기화하기 때문에 [ngrok](https://ngrok.com/) 같은 터널링 도구를 사용한 호스팅을 통해 빠르게 개발과 테스트가 가능합니다.

```python
from fastapi import FastAPI
from orbro_sdk import OrbroConnect

CLIENT_ID = 'your-client-id'
CLIENT_SECRET = 'your-client-secret'
# 발급받은 Open API 토큰이 존재하는 경우에 사용
OPEN_API_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e...'

app = FastAPI()
sdk = OrbroConnect(app, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, open_api_token=OPEN_API_TOKEN)
```

####  프로덕션(PROD) 환경에서 SDK 초기화하기
SDK는 스테이지(STG), 프로덕션(PROD) 환경에서 **클라이언트 정보 저장소를 MySQL/PostgreSQL 등의 관계형 데이터베이스로 토큰 정보 저장소는 Redis로 사용하는 것을 권장**합니다.

```python
from fastapi import FastAPI
from orbro_sdk import OrbroConnect
from orbro_sdk.models import ConnectAppClientInfoMixin
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import redis
import logging

CLIENT_ID = 'your-client-id'
CLIENT_SECRET = 'your-client-secret'
SQLALCHEMY_DATABASE_URL = 'postgresql://username:password@localhost:5432/your_app_db'

handler = logging.StreamHandler()
handler.setLevel(logging.WARNING)
logger = logging.getLogger()
logger.addHandler(handler)

Base = declarative_base()

# SQL Model
class ClientInfo(Base, ConnectAppClientInfoMixin):
    pass

engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    echo=True
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Redis = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)

# 발급받은 Open API 토큰이 존재하는 경우에 사용
OPEN_API_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e...'
app = FastAPI()
sdk = OrbroConnect(app, client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
                   db_session_maker=SessionLocal, client_info_model=ClientInfo, redis=Redis, 
                   open_api_token=OPEN_API_TOKEN)
Base.metadata.create_all(bind=engine)

```
#### FastAPI : 인증 Middleware로부터 처리된 기본 정보 얻기
* Connect 앱 SDK 인증 미들웨어로부터 모든 API에 기본적으로 전달되는 정보.
* 인증이 필요한 API는 Mobile에서 Header에  { 'sub' : '{organization.subdomain}' } 추가하여 요청 필요.
* 인증이 필요한 API는 Web에서 Query Parameter에 ?origin={origin} 추가하여 요청 필요.

```python
from fastapi import FastAPI, Request

app = FastAPI()

@app.get(
    '/my-api'
)
def get_middleware_auth_info(
    request: Request
):
    # 인증이 성공하면 request에서 아래 속성으로 접근 가능
    print(request.state.user)
    '''
        {
            'email': '',        # API를 호출한 유저의 이메일 정보 
            'origin': '',       # API를 호출한 소속의 ORIGIN 정보
            'subdomain': ''     # API를 호출한 소속의 서브도메인 정보
        }    
    '''
    return {'status_code': 200}
```

#### FastAPI : AsyncClient 디펜던시
* 플랫폼의 API를 호출 할 때 Connect 앱 설치 시에 생성된 Client 정보를 바탕으로 Connect 앱 BE 토큰을 생성하여 Dict나 Redis로 저장.
* 디펜던시로 가져왔을 때 어플리케이션에서 별도의 origin 또는 토큰 만료 시, 발급/삭제 처리 불필요.

```python
from fastapi import FastAPI, Depends
from orbro_sdk.fastapi.dependencies.client import get_async_client, AsyncWebClient

app = FastAPI()

@app.get(
    '/get-async-client'
)
async def get_async_client(
        async_client: AsyncWebClient = Depends(get_async_client)
):
    # ORBRO Platform API 호출
    # [GET]
    res = await async_client.get('/api/v1/users')
    print(res.text)
    # [PATCH]
    body = {'name': 'ORBRO'}
    res = await async_client.patch('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'),
                                   data=body)
    print(res.text)
    # [PUT]
    body = {'name': 'ORBRO'}
    res = await async_client.put('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'),
                                 data=body)
    print(res.text)

    # [POST]
    body = {'name': 'ORBRO'}
    res = await async_client.post('/api/v1/users', data=body)
    print(res.text)

    # [DELETE]
    res = await async_client.delete('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'))
    # 이외 [HEAD], [OPTIONS] 제공함.
    print(res.text)

    return { 'status_code': 200, 'message': res.text }
```

#### FastAPI : OpenAPI용 AsyncClient 디펜던시
* 플랫폼의 API를 호출 할 때 개발자가 속한 소속 정보를 바탕으로 발급된 Open API용 토큰을 통하여 API 호출.

```python
from fastapi import FastAPI, Depends
from orbro_sdk.fastapi.dependencies.client import get_openapi_async_client, AsyncWebClient

app = FastAPI()

@app.get(
    '/get-openapi-async-client'
)
async def get_openapi_async_client(
        async_client: AsyncWebClient = Depends(get_openapi_async_client)
):
    # 플랫폼 API 호출

    # [GET]
    res = await async_client.get('/api/v1/users')
    print(res.text)
    # [PATCH]
    body = {'name': 'Dragon'}
    res = await async_client.patch('/openapi/v1/users/<user_id>'.format(user_id='3a7f958f-154f-44ba-8f93-193934ed4d2a'),
                                   data=body)
    print(res.text)
    # [PUT]
    body = {'name': 'Dragon2'}
    res = await async_client.put('/openapi/v1/users/<user_id>'.format(user_id='3a7f958f-154f-44ba-8f93-193934ed4d2a'),
                                 data=body)
    print(res.text)

    # [POST]
    body = {'name': 'New Dragon'}
    res = await async_client.post('/openapi/v1/users', data=body)
    print(res.text)

    # [DELETE]
    res = await async_client.delete('/openapi/v1/users/<user_id>'.format(user_id='1a2b3c4d'))
    # 이외 [HEAD], [OPTIONS] 제공함.
    print(res.text)

    return { 'status_code': 200, 'message': res.text }
```

#### FastAPI : Webhook 디펜던시
* App Descriptor에 명시된 webhooks로 ORBRO Platform에서 Connect 앱으로 요청하는 인증에 대한 처리

```python
from fastapi import FastAPI, Depends, Response
from fastapi import Body
from orbro_sdk.fastapi.dependencies.auth import webhook_auth_dep

app = FastAPI()

@app.post(
    '/webhook/device'
)
async def handle_webhook(
        payload: dict = Body(...),
        auth: dict = Depends(webhook_auth_dep)
):
    # Webhook 메시지가 보내는 대상 앱의 조직(소속) UUID
    org_id = auth.get('organization_id')
    # Webhook 메시지가 보내는 대상 앱의 설치 사용자(Admin 이상) UUID
    user_id = auth.get('user_id')
    print(f'{org_id} / {user_id}')

    print(payload)

    return Response(status_code=200)

```

#### FastAPI : 플랫폼 인증 토큰 디펜던시
* 플랫폼에서 발급받은 Access 토큰에 대한 인증 처리 (ORBRO Platform API를 활용한 인증 위임)

```python
from fastapi import FastAPI, Depends
from orbro_sdk.fastapi.dependencies.auth import request_auth_dep

app = FastAPI()

@app.get(
    '/platform-access-token-authenticated-url'
)
def platform_authenticated_url(
    user: dict = Depends(request_auth_dep)
):
    print(user)
    '''
            {
                "uuid": "9ac14270-c141-423a-9883-504941d80f35",
                "role": "HQ",
                "status": "Activated"
            }
    '''
    return {'status_code': 200 }


```

#### FastAPI : 플랫폼 Connect 앱 인증 토큰 디펜던시
* 플랫폼에서 발급받은 Connect 앱 Access Token토큰에 대한 인증 처리 (ORBRO Platform API를 활용한 인증 위임)

```python
from fastapi import FastAPI, Depends
from orbro_sdk.fastapi.dependencies.auth import request_connect_app_auth_dep

app = FastAPI()

@app.get(
    '/connect-app-token-validation'
)
def connect_authenticated_url(
    app: dict = Depends(request_connect_app_auth_dep)
):
    print(app)
    '''
        {
            "id": "",     # SharedSecret Table id
            "uuid": """   # 설치한 User의 UUID
            "organization_id": "" # 앱의 소속 UUID 
            "role": ""    # 설치한 User의 Role
            "status": ""  # 설치한 User의 status
            "scope": ""   # 설치한 앱의 Scope
        }
    '''

    return {'status_code': 200}
```

#### FastAPI : 플랫폼 Open API 인증 토큰 디펜던시
* 플랫폼에서 발급받은 Open API 토큰에 대한 인증 처리 (ORBRO Platform API를 활용한 인증 위임)

```python
from fastapi import FastAPI, Depends
from orbro_sdk.fastapi.dependencies.auth import request_oauth_dep

app = FastAPI()

@app.get(
    '/authenticated-open-api-url'
)
def platform_open_api_authenticated_url(
    user: dict = Depends(request_oauth_dep)
):
    print(user)
    '''
            {
                "uuid": "93817f70-c141-43fa-9883-504941d80f35",
                "role": "HQ",
                "status": "Activated"
            }
    '''
    return {'status_code': 200 }
```

#### FastAPI : 유틸리티 - 토큰 저장소 디펜던시
* 토큰 저장소를 통한 토큰 저장/조회/삭제가 가능하게하는 디펜던시

```python
from fastapi import FastAPI, Depends
from orbro_sdk.models import ConnectAppToken
from orbro_sdk.datastore import DictTokenDataStore, RedisTokenDataStore
from typing import Union

app = FastAPI()

@app.get(
    '/use-token-store'
)
def handle_token(
    token_store: Union[RedisTokenDataStore, DictTokenDataStore] = Depends(OrbroConnect.token_store_dep)
):
    # 설치된 앱의 Connect 앱 토큰 정보를 Organization UUID로 조회
    app_token: ConnectAppToken = token_store.get_token('{organization.id}')
    print(app_token.organization_id)
    print(app_token.access_token)

    # Connect 앱 BE 토큰을 생성 및 저장
    token = OrbroConnect.token_util.issue_token('{shared_secret_key}', '{insatlled_user_uuid}')
    token_store.set_token({
        'organization_id': '',   # Connect 앱 토큰을 저장할 Organization ID,
        'access_token': token      # 발급한 토큰 정보
    })

    # Connect 앱 BE 토큰을 삭제
    token_store.delete('{organization_uuid}')

    return {'status_code': 200}
```
#### FastAPI : 유틸리티 - 클라이언트 정보 저장소 디펜던시
* 클라이언트 정보 저장소를 통한 클라이언트 정보 저장/조회/삭제가 가능하게하는 디펜던시

```python
from fastapi import FastAPI, Depends
from orbro_sdk.datastore import ClientInfoDataStore

app = FastAPI()

@app.get(
    '/use-client-datastore',
    description="앱의 클라이언트 정보를 핸들링 할 때 사용하는 디펜던시"
)
def handle_client_info(
    client_datastore: ClientInfoDataStore = Depends(OrbroConnect.client_info_store_dep)
):

    # 앱을 설치한 클라이언트 정보를 Organization UUID로 조회
    app_client_info: ClientInfo = client_datastore.get_by_organization_id('{organization.id}')

    # 앱을 설치한 클라이언트 정보를 Organization subdomain으로 조회
    app_client_info: ClientInfo = client_datastore.get_by_subdomain('{organization.subdomain}')

    # 앱을 설치한 클라이언트 정보를 앱을 설치한 User의 UUID로 조회
    app_client_info: ClientInfo = client_datastore.get_by_user_id('{user.id}')

    # 일반적으로 앱 설치/삭제 시, 클라이언트 정보의 생성과 삭제는 /installed 나 /installed가 호출될 때 middleware에서 자동 처리됨.
    # 앱 설치/삭제 외 사용이 필요하면 아래와 같이 사용
    # 생성
    client_datastore.add({
        'orgamization_id': '',    # 앱을 설치한 조직(소속)의 UUID
        'shared_secret': '',      # 플랫폼에서 앱 배포 시, 생성된 shared_secret 키
        'installed_user_id': '',  # 앱을 설치한 사용자의 UUID
        'subdomain': ''           # 앱을 설치한 조직(소속)의 서브 도메인
    })
    client_datastore.commit()

    # 삭제
    deleted_app_client_info: ClientInfo = client_datastore.get_by_organization_id('{organization.id}')
    client_datastore.delete(deleted_app_client_info)
    client_datastore.commit()

    return {'status_code': 200}
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://www.notion.so/ORBRO-Connect-7065dd4435264beebdeb52c7f7408820",
    "name": "orbro-python-sdk",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "orbro connect iot digitaltwin rtls",
    "author": "ORBRO",
    "author_email": "platform.dev@kong-tech.com",
    "download_url": "https://files.pythonhosted.org/packages/b4/be/2bc140098365e72f559517693552cfa1a0c6990dd118794dab92839bf9e1/orbro-python-sdk-0.19.tar.gz",
    "platform": null,
    "description": "# README #\r\n [ORBRO Platform](https://orbro.io)\ub294 \uc571\uc744 \uac1c\ubc1c\ud558\uae30 \uc704\ud55c \ub2e4\uc591\ud55c API\ub97c \uc81c\uacf5\ud558\uba70, \uc704\uc82f\uacfc \uc571 \ud615\ud0dc\ub85c ORBRO Platform \uc0c1\uc5d0 \ud558\ub098\uc758 \ud1b5\ud569\ub41c UI\ub85c \ubc30\ud3ec\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc30\ud3ec\ub41c \uc571\ub4e4\uc740 ORBRO Platform\uc744 \uc0ac\uc6a9\ud558\ub294 \ubaa8\ub4e0 \uae30\uc5c5/\uae30\uad00/\uc0ac\uc6a9\uc790\uc5d0\uac8c \uacf5\uac1c\ud558\uac70\ub098 \ubcf8\uc778\uc774 \uc18c\uc18d\ud55c \uc870\uc9c1\uc5d0\uc11c\ub9cc \ud65c\uc6a9\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.  \r\n\r\n\ubcf8 \ud30c\uc774\uc36c SDK\ub294 FastAPI\uc640 Flask \ub4f1\uc758 \uc8fc\uc694 Python Web Framework\uc5d0\uc11c ORBRO Platform\uc758 Connect \uc571 \uac1c\ubc1c\uc744 \uc704\ud574 \ud50c\ub7ab\ud3fc \uc778\uc99d \uad00\ub9ac\ub97c \ud3b8\ud558\uac8c \ud574\uc8fc\uace0 \ud50c\ub7ab\ud3fc API\uc758 \ud638\ucd9c\uc744 \uac00\ub2a5\ud558\uac8c \ud569\ub2c8\ub2e4. \r\n\r\n### Requirements\r\n\r\n---\r\nPython 3.7+\r\n\r\n### Installation\r\n\r\n[PyPI][pypi] \uc744 \ud1b5\ud55c \uc124\uce58\ub97c \uad8c\uc7a5\ud569\ub2c8\ub2e4.\r\n\r\n```bash\r\n$ pip install orbro-python-sdk\r\n```\r\n\r\n### \uc2dc\uc791\ud558\uae30\r\n\r\n---\r\n####  SDK\uac00 \uc81c\uacf5\ud558\ub294 \uae30\ub2a5\r\n**ORBRO Connect Python SDK**\ub294 \uc544\ub798\uc640 \uac19\uc740 \uae30\ub2a5\ub4e4\uc774 \ub0b4\uc7a5\ub418\uc5b4\uc788\uac70\ub098 \uc9c0\uc6d0\ud569\ub2c8\ub2e4.\r\n\r\n- `orbro_sdk`: FastAPI/Flask \ub4f1\uc758 \uc5b4\ud50c\ub9ac\ucf00\uc774\uc158\uc73c\ub85c \ucd08\uae30\ud654\ub418\ub294 OrbroConnect class '\uc81c\uacf5'\r\n- `orbro_sdk.datastore`: SQLAlchemy/SQL \uae30\ubc18\uc758 Client \uc815\ubcf4 \uc800\uc7a5\uc18c, Dict/Redis \uae30\ubc18\uc758 Access Token \uc800\uc7a5\uc18c '\uc81c\uacf5' \r\n- `orbro_sdk.models`: Connect \uc571 \ud1a0\ud070, Lifecycle\uc5d0 \ub530\ub978 \uc124\uce58/\uc0ad\uc81c Request, Client \uc815\ubcf4 Mixin \ubaa8\ub378 '\uc81c\uacf5'\r\n- `orbro_sdk.fastapi.middleware`: FastAPI\uc6a9. Web/Mobile \ub4f1\uc774 SDK\ub97c \uc0ac\uc6a9\ud55c Connect \uc571(BE)\uc5d0 \uc694\uccad(Request)\ud560 \ub54c\uc758 \uc778\uc99d \uc804\ucc98\ub9ac \ubbf8\ub4e4\uc6e8\uc5b4 '\ub0b4\uc7a5'\r\n- `orbro_sdk.fastapi.api_router`: FastAPI\uc6a9. App Descriptor\uc5d0 \uba85\uc2dc\ub41c lifecycle.installed, lifecycle.uninstalled URL\uc5d0 \ub530\ub978 ORBRO \ud50c\ub7ab\ud3fc Webhook \ud638\ucd9c \uc694\uccad(Request) \ubc0f Client \uc815\ubcf4\uc5d0 \ub300\ud55c \ucc98\ub9ac '\ub0b4\uc7a5'\r\n- `orbro_sdk.fastapi.dependencies`: FastAPI\uc6a9. DI\ub85c \uc0ac\uc6a9\ud558\uae30\uc704\ud55c \uc778\uc99d, \uc800\uc7a5\uc18c, API \ud638\ucd9c\uc774 \uac00\ub2a5\ud55c AsyncWebClient\uc758 Dependencies '\uc81c\uacf5'\r\n\r\n####  App Descriptor\r\nconnect.json\uacfc \uac19\uc740 JSON \ud615\uc2dd\uc758 \uc571 Descriptor \ud30c\uc77c \uc791\uc131\uc744 \ud1b5\ud574 ORBRO Platform\uc5d0 Connect \uc571\uc744 \ubc30\ud3ec\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\r\nSDK \uae30\ubcf8\uc801\uc73c\ub85c ORBRO Platform\uc5d0\uc11c \ubc1c\uae09\ub41c  Client ID\uc640 Client Seceret\uacfc \ud558\ub2e8\uc758 \uc571 Descriptor \ud30c\uc77c\uc744 \uae30\ubc18\uc73c\ub85c \ub3d9\uc791\ud569\ub2c8\ub2e4.\r\n\ub354 \uc0c1\uc138\ud55c \ub0b4\uc6a9\uc740 [Developer Guide - ORBRO Connect](https://orbro.notion.site/ORBRO-Connect-7065dd4435264beebdeb52c7f7408820)\uc5d0\uc11c \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\r\n\r\n```json\r\n{\r\n  \"id\": \"com.kongtech.sdk.tester\",\r\n  \"name\": \"SDK Test App.\",\r\n  \"description\": \"\ub85c\uceec \ubc0f \uac1c\ubc1c \uc11c\ubc84\uc5d0\uc11c\uc758 SDK \ud14c\uc2a4\ud2b8\",\r\n  \"vendor\": {\r\n    \"name\": \"Kongtech\",\r\n    \"url\": \"https://home.orbro.io\"\r\n  },\r\n  \"baseUrl\": \"https://f5cd-210-97-92-56.ngrok.io\",\r\n  \"lifecycle\": {\r\n    \"installed\": \"/installed\",\r\n    \"uninstalled\": \"/uninstalled\",\r\n    \"enabled\": \"/enabled\",\r\n    \"disabled\": \"/disabled\"\r\n  },\r\n  \"scopes\": [\r\n    \"every\"\r\n  ],\r\n  \"modules\": {\r\n    \"webhooks\": [\r\n      {\r\n        \"event_name\": \"device:device_updated\",\r\n        \"callback_api\": \"https://f5cd-210-97-92-56.ngrok.io/webhook/device\",\r\n        \"property\": \"profile_id\",\r\n        \"value\": \"Fixed.UWB.Anchor\"\r\n      },\r\n      {\r\n        \"event_name\": \"device:device_updated\",\r\n        \"callback_api\": \"https://f5cd-210-97-92-56.ngrok.io/webhook/device\",\r\n        \"property\": \"profile_id\",\r\n        \"value\": \"Movable.IndoorCamera.RTLS\"\r\n      }\r\n    ]\r\n  },\r\n  \"version\": \"0.1\"\r\n}\r\n```\r\n\r\n####  \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \ubaa8\ub378 \uc815\uc758\r\nORBRO Platform\uc5d0\uc11c \uc571 \uc124\uce58/\uc0ad\uc81c \uc2dc\uc5d0 lifecycle webhook\uc73c\ub85c \uc804\ub2ec\ub418\ub294 \uc815\ubcf4\ub97c \uae30\ubc18\uc73c\ub85c \uc571\uc744 \uc124\uce58\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\uac00 \uad00\ub9ac\ub429\ub2c8\ub2e4.\r\n\r\n```python\r\nfrom orbro_sdk.models import ConnectAppClientInfoMixin\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\nBase = declarative_base()\r\n\r\n# SQL Model\r\nclass ClientInfo(Base, ConnectAppClientInfoMixin):\r\n    pass\r\n\r\n'''\r\n    class ConnectAppClientInfoMixin:\r\n        \"\"\"Connect App Shared Secret Info\"\"\"\r\n        __tablename__ = 'connect_app_client_info'\r\n        id = Column(Integer, primary_key=True)\r\n        organization_id = Column(String(255), nullable=False)\r\n        shared_secret = Column(String(255), nullable=False)\r\n        subdomain = Column(String(255))\r\n        installed_user_id = Column(String(255))\r\n        created_time = Column(DateTime, default=func.now())\r\n        updated_time = Column(DateTime, default=func.now())\r\n'''\r\n```\r\n####  \uac1c\ubc1c(DEV) \ud658\uacbd\uc5d0\uc11c SDK \ucd08\uae30\ud654\ud558\uae30\r\nSDK\ub294 \ub85c\uceec(Local) \ub610\ub294 \uac1c\ubc1c(DEV) \ud658\uacbd\uc5d0\uc11c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \uc800\uc7a5\uc18c\ub97c sqlite\ub85c \ud1a0\ud070 \uc815\ubcf4 \uc800\uc7a5\uc18c\ub294 Dictionary\ub85c \uc790\ub3d9\uc73c\ub85c \ucd08\uae30\ud654\ud558\uae30 \ub54c\ubb38\uc5d0 [ngrok](https://ngrok.com/) \uac19\uc740 \ud130\ub110\ub9c1 \ub3c4\uad6c\ub97c \uc0ac\uc6a9\ud55c \ud638\uc2a4\ud305\uc744 \ud1b5\ud574 \ube60\ub974\uac8c \uac1c\ubc1c\uacfc \ud14c\uc2a4\ud2b8\uac00 \uac00\ub2a5\ud569\ub2c8\ub2e4.\r\n\r\n```python\r\nfrom fastapi import FastAPI\r\nfrom orbro_sdk import OrbroConnect\r\n\r\nCLIENT_ID = 'your-client-id'\r\nCLIENT_SECRET = 'your-client-secret'\r\n# \ubc1c\uae09\ubc1b\uc740 Open API \ud1a0\ud070\uc774 \uc874\uc7ac\ud558\ub294 \uacbd\uc6b0\uc5d0 \uc0ac\uc6a9\r\nOPEN_API_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e...'\r\n\r\napp = FastAPI()\r\nsdk = OrbroConnect(app, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, open_api_token=OPEN_API_TOKEN)\r\n```\r\n\r\n####  \ud504\ub85c\ub355\uc158(PROD) \ud658\uacbd\uc5d0\uc11c SDK \ucd08\uae30\ud654\ud558\uae30\r\nSDK\ub294 \uc2a4\ud14c\uc774\uc9c0(STG), \ud504\ub85c\ub355\uc158(PROD) \ud658\uacbd\uc5d0\uc11c **\ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \uc800\uc7a5\uc18c\ub97c MySQL/PostgreSQL \ub4f1\uc758 \uad00\uacc4\ud615 \ub370\uc774\ud130\ubca0\uc774\uc2a4\ub85c \ud1a0\ud070 \uc815\ubcf4 \uc800\uc7a5\uc18c\ub294 Redis\ub85c \uc0ac\uc6a9\ud558\ub294 \uac83\uc744 \uad8c\uc7a5**\ud569\ub2c8\ub2e4.\r\n\r\n```python\r\nfrom fastapi import FastAPI\r\nfrom orbro_sdk import OrbroConnect\r\nfrom orbro_sdk.models import ConnectAppClientInfoMixin\r\nfrom sqlalchemy import create_engine\r\nfrom sqlalchemy.orm import sessionmaker\r\nfrom sqlalchemy.ext.declarative import declarative_base\r\nimport redis\r\nimport logging\r\n\r\nCLIENT_ID = 'your-client-id'\r\nCLIENT_SECRET = 'your-client-secret'\r\nSQLALCHEMY_DATABASE_URL = 'postgresql://username:password@localhost:5432/your_app_db'\r\n\r\nhandler = logging.StreamHandler()\r\nhandler.setLevel(logging.WARNING)\r\nlogger = logging.getLogger()\r\nlogger.addHandler(handler)\r\n\r\nBase = declarative_base()\r\n\r\n# SQL Model\r\nclass ClientInfo(Base, ConnectAppClientInfoMixin):\r\n    pass\r\n\r\nengine = create_engine(\r\n    SQLALCHEMY_DATABASE_URL,\r\n    echo=True\r\n)\r\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\r\n\r\nRedis = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)\r\n\r\n# \ubc1c\uae09\ubc1b\uc740 Open API \ud1a0\ud070\uc774 \uc874\uc7ac\ud558\ub294 \uacbd\uc6b0\uc5d0 \uc0ac\uc6a9\r\nOPEN_API_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e...'\r\napp = FastAPI()\r\nsdk = OrbroConnect(app, client_id=CLIENT_ID, client_secret=CLIENT_SECRET,\r\n                   db_session_maker=SessionLocal, client_info_model=ClientInfo, redis=Redis, \r\n                   open_api_token=OPEN_API_TOKEN)\r\nBase.metadata.create_all(bind=engine)\r\n\r\n```\r\n#### FastAPI : \uc778\uc99d Middleware\ub85c\ubd80\ud130 \ucc98\ub9ac\ub41c \uae30\ubcf8 \uc815\ubcf4 \uc5bb\uae30\r\n* Connect \uc571 SDK \uc778\uc99d \ubbf8\ub4e4\uc6e8\uc5b4\ub85c\ubd80\ud130 \ubaa8\ub4e0 API\uc5d0 \uae30\ubcf8\uc801\uc73c\ub85c \uc804\ub2ec\ub418\ub294 \uc815\ubcf4.\r\n* \uc778\uc99d\uc774 \ud544\uc694\ud55c API\ub294 Mobile\uc5d0\uc11c Header\uc5d0  { 'sub' : '{organization.subdomain}' } \ucd94\uac00\ud558\uc5ec \uc694\uccad \ud544\uc694.\r\n* \uc778\uc99d\uc774 \ud544\uc694\ud55c API\ub294 Web\uc5d0\uc11c Query Parameter\uc5d0 ?origin={origin} \ucd94\uac00\ud558\uc5ec \uc694\uccad \ud544\uc694.\r\n\r\n```python\r\nfrom fastapi import FastAPI, Request\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/my-api'\r\n)\r\ndef get_middleware_auth_info(\r\n    request: Request\r\n):\r\n    # \uc778\uc99d\uc774 \uc131\uacf5\ud558\uba74 request\uc5d0\uc11c \uc544\ub798 \uc18d\uc131\uc73c\ub85c \uc811\uadfc \uac00\ub2a5\r\n    print(request.state.user)\r\n    '''\r\n        {\r\n            'email': '',        # API\ub97c \ud638\ucd9c\ud55c \uc720\uc800\uc758 \uc774\uba54\uc77c \uc815\ubcf4 \r\n            'origin': '',       # API\ub97c \ud638\ucd9c\ud55c \uc18c\uc18d\uc758 ORIGIN \uc815\ubcf4\r\n            'subdomain': ''     # API\ub97c \ud638\ucd9c\ud55c \uc18c\uc18d\uc758 \uc11c\ube0c\ub3c4\uba54\uc778 \uc815\ubcf4\r\n        }    \r\n    '''\r\n    return {'status_code': 200}\r\n```\r\n\r\n#### FastAPI : AsyncClient \ub514\ud39c\ub358\uc2dc\r\n* \ud50c\ub7ab\ud3fc\uc758 API\ub97c \ud638\ucd9c \ud560 \ub54c Connect \uc571 \uc124\uce58 \uc2dc\uc5d0 \uc0dd\uc131\ub41c Client \uc815\ubcf4\ub97c \ubc14\ud0d5\uc73c\ub85c Connect \uc571 BE \ud1a0\ud070\uc744 \uc0dd\uc131\ud558\uc5ec Dict\ub098 Redis\ub85c \uc800\uc7a5.\r\n* \ub514\ud39c\ub358\uc2dc\ub85c \uac00\uc838\uc654\uc744 \ub54c \uc5b4\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0\uc11c \ubcc4\ub3c4\uc758 origin \ub610\ub294 \ud1a0\ud070 \ub9cc\ub8cc \uc2dc, \ubc1c\uae09/\uc0ad\uc81c \ucc98\ub9ac \ubd88\ud544\uc694.\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.fastapi.dependencies.client import get_async_client, AsyncWebClient\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/get-async-client'\r\n)\r\nasync def get_async_client(\r\n        async_client: AsyncWebClient = Depends(get_async_client)\r\n):\r\n    # ORBRO Platform API \ud638\ucd9c\r\n    # [GET]\r\n    res = await async_client.get('/api/v1/users')\r\n    print(res.text)\r\n    # [PATCH]\r\n    body = {'name': 'ORBRO'}\r\n    res = await async_client.patch('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'),\r\n                                   data=body)\r\n    print(res.text)\r\n    # [PUT]\r\n    body = {'name': 'ORBRO'}\r\n    res = await async_client.put('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'),\r\n                                 data=body)\r\n    print(res.text)\r\n\r\n    # [POST]\r\n    body = {'name': 'ORBRO'}\r\n    res = await async_client.post('/api/v1/users', data=body)\r\n    print(res.text)\r\n\r\n    # [DELETE]\r\n    res = await async_client.delete('/api/v1/users/<user_id>'.format(user_id='3a71428f-1aaf-4ccc-8fff-193934ed4d2a'))\r\n    # \uc774\uc678 [HEAD], [OPTIONS] \uc81c\uacf5\ud568.\r\n    print(res.text)\r\n\r\n    return { 'status_code': 200, 'message': res.text }\r\n```\r\n\r\n#### FastAPI : OpenAPI\uc6a9 AsyncClient \ub514\ud39c\ub358\uc2dc\r\n* \ud50c\ub7ab\ud3fc\uc758 API\ub97c \ud638\ucd9c \ud560 \ub54c \uac1c\ubc1c\uc790\uac00 \uc18d\ud55c \uc18c\uc18d \uc815\ubcf4\ub97c \ubc14\ud0d5\uc73c\ub85c \ubc1c\uae09\ub41c Open API\uc6a9 \ud1a0\ud070\uc744 \ud1b5\ud558\uc5ec API \ud638\ucd9c.\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.fastapi.dependencies.client import get_openapi_async_client, AsyncWebClient\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/get-openapi-async-client'\r\n)\r\nasync def get_openapi_async_client(\r\n        async_client: AsyncWebClient = Depends(get_openapi_async_client)\r\n):\r\n    # \ud50c\ub7ab\ud3fc API \ud638\ucd9c\r\n\r\n    # [GET]\r\n    res = await async_client.get('/api/v1/users')\r\n    print(res.text)\r\n    # [PATCH]\r\n    body = {'name': 'Dragon'}\r\n    res = await async_client.patch('/openapi/v1/users/<user_id>'.format(user_id='3a7f958f-154f-44ba-8f93-193934ed4d2a'),\r\n                                   data=body)\r\n    print(res.text)\r\n    # [PUT]\r\n    body = {'name': 'Dragon2'}\r\n    res = await async_client.put('/openapi/v1/users/<user_id>'.format(user_id='3a7f958f-154f-44ba-8f93-193934ed4d2a'),\r\n                                 data=body)\r\n    print(res.text)\r\n\r\n    # [POST]\r\n    body = {'name': 'New Dragon'}\r\n    res = await async_client.post('/openapi/v1/users', data=body)\r\n    print(res.text)\r\n\r\n    # [DELETE]\r\n    res = await async_client.delete('/openapi/v1/users/<user_id>'.format(user_id='1a2b3c4d'))\r\n    # \uc774\uc678 [HEAD], [OPTIONS] \uc81c\uacf5\ud568.\r\n    print(res.text)\r\n\r\n    return { 'status_code': 200, 'message': res.text }\r\n```\r\n\r\n#### FastAPI : Webhook \ub514\ud39c\ub358\uc2dc\r\n* App Descriptor\uc5d0 \uba85\uc2dc\ub41c webhooks\ub85c ORBRO Platform\uc5d0\uc11c Connect \uc571\uc73c\ub85c \uc694\uccad\ud558\ub294 \uc778\uc99d\uc5d0 \ub300\ud55c \ucc98\ub9ac\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends, Response\r\nfrom fastapi import Body\r\nfrom orbro_sdk.fastapi.dependencies.auth import webhook_auth_dep\r\n\r\napp = FastAPI()\r\n\r\n@app.post(\r\n    '/webhook/device'\r\n)\r\nasync def handle_webhook(\r\n        payload: dict = Body(...),\r\n        auth: dict = Depends(webhook_auth_dep)\r\n):\r\n    # Webhook \uba54\uc2dc\uc9c0\uac00 \ubcf4\ub0b4\ub294 \ub300\uc0c1 \uc571\uc758 \uc870\uc9c1(\uc18c\uc18d) UUID\r\n    org_id = auth.get('organization_id')\r\n    # Webhook \uba54\uc2dc\uc9c0\uac00 \ubcf4\ub0b4\ub294 \ub300\uc0c1 \uc571\uc758 \uc124\uce58 \uc0ac\uc6a9\uc790(Admin \uc774\uc0c1) UUID\r\n    user_id = auth.get('user_id')\r\n    print(f'{org_id} / {user_id}')\r\n\r\n    print(payload)\r\n\r\n    return Response(status_code=200)\r\n\r\n```\r\n\r\n#### FastAPI : \ud50c\ub7ab\ud3fc \uc778\uc99d \ud1a0\ud070 \ub514\ud39c\ub358\uc2dc\r\n* \ud50c\ub7ab\ud3fc\uc5d0\uc11c \ubc1c\uae09\ubc1b\uc740 Access \ud1a0\ud070\uc5d0 \ub300\ud55c \uc778\uc99d \ucc98\ub9ac (ORBRO Platform API\ub97c \ud65c\uc6a9\ud55c \uc778\uc99d \uc704\uc784)\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.fastapi.dependencies.auth import request_auth_dep\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/platform-access-token-authenticated-url'\r\n)\r\ndef platform_authenticated_url(\r\n    user: dict = Depends(request_auth_dep)\r\n):\r\n    print(user)\r\n    '''\r\n            {\r\n                \"uuid\": \"9ac14270-c141-423a-9883-504941d80f35\",\r\n                \"role\": \"HQ\",\r\n                \"status\": \"Activated\"\r\n            }\r\n    '''\r\n    return {'status_code': 200 }\r\n\r\n\r\n```\r\n\r\n#### FastAPI : \ud50c\ub7ab\ud3fc Connect \uc571 \uc778\uc99d \ud1a0\ud070 \ub514\ud39c\ub358\uc2dc\r\n* \ud50c\ub7ab\ud3fc\uc5d0\uc11c \ubc1c\uae09\ubc1b\uc740 Connect \uc571 Access Token\ud1a0\ud070\uc5d0 \ub300\ud55c \uc778\uc99d \ucc98\ub9ac (ORBRO Platform API\ub97c \ud65c\uc6a9\ud55c \uc778\uc99d \uc704\uc784)\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.fastapi.dependencies.auth import request_connect_app_auth_dep\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/connect-app-token-validation'\r\n)\r\ndef connect_authenticated_url(\r\n    app: dict = Depends(request_connect_app_auth_dep)\r\n):\r\n    print(app)\r\n    '''\r\n        {\r\n            \"id\": \"\",     # SharedSecret Table id\r\n            \"uuid\": \"\"\"   # \uc124\uce58\ud55c User\uc758 UUID\r\n            \"organization_id\": \"\" # \uc571\uc758 \uc18c\uc18d UUID \r\n            \"role\": \"\"    # \uc124\uce58\ud55c User\uc758 Role\r\n            \"status\": \"\"  # \uc124\uce58\ud55c User\uc758 status\r\n            \"scope\": \"\"   # \uc124\uce58\ud55c \uc571\uc758 Scope\r\n        }\r\n    '''\r\n\r\n    return {'status_code': 200}\r\n```\r\n\r\n#### FastAPI : \ud50c\ub7ab\ud3fc Open API \uc778\uc99d \ud1a0\ud070 \ub514\ud39c\ub358\uc2dc\r\n* \ud50c\ub7ab\ud3fc\uc5d0\uc11c \ubc1c\uae09\ubc1b\uc740 Open API \ud1a0\ud070\uc5d0 \ub300\ud55c \uc778\uc99d \ucc98\ub9ac (ORBRO Platform API\ub97c \ud65c\uc6a9\ud55c \uc778\uc99d \uc704\uc784)\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.fastapi.dependencies.auth import request_oauth_dep\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/authenticated-open-api-url'\r\n)\r\ndef platform_open_api_authenticated_url(\r\n    user: dict = Depends(request_oauth_dep)\r\n):\r\n    print(user)\r\n    '''\r\n            {\r\n                \"uuid\": \"93817f70-c141-43fa-9883-504941d80f35\",\r\n                \"role\": \"HQ\",\r\n                \"status\": \"Activated\"\r\n            }\r\n    '''\r\n    return {'status_code': 200 }\r\n```\r\n\r\n#### FastAPI : \uc720\ud2f8\ub9ac\ud2f0 - \ud1a0\ud070 \uc800\uc7a5\uc18c \ub514\ud39c\ub358\uc2dc\r\n* \ud1a0\ud070 \uc800\uc7a5\uc18c\ub97c \ud1b5\ud55c \ud1a0\ud070 \uc800\uc7a5/\uc870\ud68c/\uc0ad\uc81c\uac00 \uac00\ub2a5\ud558\uac8c\ud558\ub294 \ub514\ud39c\ub358\uc2dc\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.models import ConnectAppToken\r\nfrom orbro_sdk.datastore import DictTokenDataStore, RedisTokenDataStore\r\nfrom typing import Union\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/use-token-store'\r\n)\r\ndef handle_token(\r\n    token_store: Union[RedisTokenDataStore, DictTokenDataStore] = Depends(OrbroConnect.token_store_dep)\r\n):\r\n    # \uc124\uce58\ub41c \uc571\uc758 Connect \uc571 \ud1a0\ud070 \uc815\ubcf4\ub97c Organization UUID\ub85c \uc870\ud68c\r\n    app_token: ConnectAppToken = token_store.get_token('{organization.id}')\r\n    print(app_token.organization_id)\r\n    print(app_token.access_token)\r\n\r\n    # Connect \uc571 BE \ud1a0\ud070\uc744 \uc0dd\uc131 \ubc0f \uc800\uc7a5\r\n    token = OrbroConnect.token_util.issue_token('{shared_secret_key}', '{insatlled_user_uuid}')\r\n    token_store.set_token({\r\n        'organization_id': '',   # Connect \uc571 \ud1a0\ud070\uc744 \uc800\uc7a5\ud560 Organization ID,\r\n        'access_token': token      # \ubc1c\uae09\ud55c \ud1a0\ud070 \uc815\ubcf4\r\n    })\r\n\r\n    # Connect \uc571 BE \ud1a0\ud070\uc744 \uc0ad\uc81c\r\n    token_store.delete('{organization_uuid}')\r\n\r\n    return {'status_code': 200}\r\n```\r\n#### FastAPI : \uc720\ud2f8\ub9ac\ud2f0 - \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \uc800\uc7a5\uc18c \ub514\ud39c\ub358\uc2dc\r\n* \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \uc800\uc7a5\uc18c\ub97c \ud1b5\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 \uc800\uc7a5/\uc870\ud68c/\uc0ad\uc81c\uac00 \uac00\ub2a5\ud558\uac8c\ud558\ub294 \ub514\ud39c\ub358\uc2dc\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom orbro_sdk.datastore import ClientInfoDataStore\r\n\r\napp = FastAPI()\r\n\r\n@app.get(\r\n    '/use-client-datastore',\r\n    description=\"\uc571\uc758 \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c \ud578\ub4e4\ub9c1 \ud560 \ub54c \uc0ac\uc6a9\ud558\ub294 \ub514\ud39c\ub358\uc2dc\"\r\n)\r\ndef handle_client_info(\r\n    client_datastore: ClientInfoDataStore = Depends(OrbroConnect.client_info_store_dep)\r\n):\r\n\r\n    # \uc571\uc744 \uc124\uce58\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c Organization UUID\ub85c \uc870\ud68c\r\n    app_client_info: ClientInfo = client_datastore.get_by_organization_id('{organization.id}')\r\n\r\n    # \uc571\uc744 \uc124\uce58\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c Organization subdomain\uc73c\ub85c \uc870\ud68c\r\n    app_client_info: ClientInfo = client_datastore.get_by_subdomain('{organization.subdomain}')\r\n\r\n    # \uc571\uc744 \uc124\uce58\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c \uc571\uc744 \uc124\uce58\ud55c User\uc758 UUID\ub85c \uc870\ud68c\r\n    app_client_info: ClientInfo = client_datastore.get_by_user_id('{user.id}')\r\n\r\n    # \uc77c\ubc18\uc801\uc73c\ub85c \uc571 \uc124\uce58/\uc0ad\uc81c \uc2dc, \ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\uc758 \uc0dd\uc131\uacfc \uc0ad\uc81c\ub294 /installed \ub098 /installed\uac00 \ud638\ucd9c\ub420 \ub54c middleware\uc5d0\uc11c \uc790\ub3d9 \ucc98\ub9ac\ub428.\r\n    # \uc571 \uc124\uce58/\uc0ad\uc81c \uc678 \uc0ac\uc6a9\uc774 \ud544\uc694\ud558\uba74 \uc544\ub798\uc640 \uac19\uc774 \uc0ac\uc6a9\r\n    # \uc0dd\uc131\r\n    client_datastore.add({\r\n        'orgamization_id': '',    # \uc571\uc744 \uc124\uce58\ud55c \uc870\uc9c1(\uc18c\uc18d)\uc758 UUID\r\n        'shared_secret': '',      # \ud50c\ub7ab\ud3fc\uc5d0\uc11c \uc571 \ubc30\ud3ec \uc2dc, \uc0dd\uc131\ub41c shared_secret \ud0a4\r\n        'installed_user_id': '',  # \uc571\uc744 \uc124\uce58\ud55c \uc0ac\uc6a9\uc790\uc758 UUID\r\n        'subdomain': ''           # \uc571\uc744 \uc124\uce58\ud55c \uc870\uc9c1(\uc18c\uc18d)\uc758 \uc11c\ube0c \ub3c4\uba54\uc778\r\n    })\r\n    client_datastore.commit()\r\n\r\n    # \uc0ad\uc81c\r\n    deleted_app_client_info: ClientInfo = client_datastore.get_by_organization_id('{organization.id}')\r\n    client_datastore.delete(deleted_app_client_info)\r\n    client_datastore.commit()\r\n\r\n    return {'status_code': 200}\r\n```\r\n",
    "bugtrack_url": null,
    "license": "Apache License 2.0",
    "summary": "ORBRO-Connect SDK for Python(FastAPI/Flask)",
    "version": "0.19",
    "split_keywords": [
        "orbro",
        "connect",
        "iot",
        "digitaltwin",
        "rtls"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c42a33b4a266cfb40facdc82609cc30781944fca1aaa3a3d7bf457200145e887",
                "md5": "c9b5e8d0477cb57f1544539799394d41",
                "sha256": "40d76fa7701ef8012614ad2593a4e2b648417f371cecc98476c05d33aad4453a"
            },
            "downloads": -1,
            "filename": "orbro_python_sdk-0.19-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c9b5e8d0477cb57f1544539799394d41",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 23313,
            "upload_time": "2023-02-06T05:42:46",
            "upload_time_iso_8601": "2023-02-06T05:42:46.354693Z",
            "url": "https://files.pythonhosted.org/packages/c4/2a/33b4a266cfb40facdc82609cc30781944fca1aaa3a3d7bf457200145e887/orbro_python_sdk-0.19-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b4be2bc140098365e72f559517693552cfa1a0c6990dd118794dab92839bf9e1",
                "md5": "905938eb353bd917bab8263ea071b369",
                "sha256": "6ab54f42dd93d2f462d02fc7a4d0d0b64df912118ab38c69c9a1382460074b81"
            },
            "downloads": -1,
            "filename": "orbro-python-sdk-0.19.tar.gz",
            "has_sig": false,
            "md5_digest": "905938eb353bd917bab8263ea071b369",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 17843,
            "upload_time": "2023-02-06T05:42:48",
            "upload_time_iso_8601": "2023-02-06T05:42:48.351104Z",
            "url": "https://files.pythonhosted.org/packages/b4/be/2bc140098365e72f559517693552cfa1a0c6990dd118794dab92839bf9e1/orbro-python-sdk-0.19.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-06 05:42:48",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "orbro-python-sdk"
}
        
Elapsed time: 0.04523s