Skip to content

Commit

Permalink
Version 0.3.1 (#49)
Browse files Browse the repository at this point in the history
* feat: add basic structure

* feat: add few ocpi data types

* feat: add data types

* feat: add version module enums and schemas

* feat: add basic versions module

* feat: add versions details endpoint

* refactor: modify VersionNumber enum

* feat: add locations module, crud and adaptor

* chore: update prospector config

* refactor: adaptor

* refactor: adaptor

* chore: update readme

* feat: make crud and adapter dependency, add example

* refactor: import core enums and datatypes to source module

* refactor: make routers and CRUD methods asycn

* fix: linter

* fix: update schema definitions

* fix: update schema definitions

* chore: add pypi publish workflow

* refactor: change project name to py_ocpi

* Update README.md

* refactor: get prefix from env

* refactor: change HOST to OCPI_HOST

* feat: add pagination for list endpoints

* fix: add id param to update method of crud

* Update README.md

* Rename README.md to README.rst

* Update README.rst

* Update pyproject.toml

* OCPI-55 (#7)

* feat: add sessions module schemas and enums

* feat: add sessions module schemas and enums

* feat: add sessions endpoints

* OCPI-48 (#3)

* feat: add schemas and enums for credentials module

* fix: pylint fix

* feat: add credentials module

* fix: PR review changes

* feat: make crud funtions in credentials endpoint async

* fix: change verification params

* feat: add cdrs schemas and enums (#10)

* feat: add cdrs endpoints, refactor code enums, move universal files to core (#11)

* OCPI-48 (#15)

* feat: update authorization process

* fix: pylint fix

* feat: add exception handlder

* fix: pylint, add middleware fix

* feat: add all test modules (#17)

* feat: change post credentials url

* feat: modify commands module cpo (#18)

* OCPI-66 (#22)

* feat: improve crud and adapter, improve OCPI version management

* refactor: add routers and modules directories

* Fix the router URLs (#36)

* Fix the router URLs

* Fix versions and details URLs

* fix: return version details based on role (#39)

* Use dict type instead of list for details endpoint (#40)

* Fix the initial authentication (#43)

* Base64 encode the bearer token

* Decode the base64 encoded authentication token

* Require authorization when listing versions

* Revert "Fix the initial authentication (#43)" (#44)

This reverts commit a40dc0d.

* Update push.yaml

* Fix initial credentials exchange (#46)

* Fix the initial authentication (#43)

* Base64 encode the bearer token

* Decode the base64 encoded authentication token

* Require authorization when listing versions

* Remove the list wrapping the credentials object

* Fix tests

* Skip false positive Bandit test B105

* Ignore false positive

* Fix linter error

* feat: make new version 0.3.1

---------

Co-authored-by: Hamed Akhavan <[email protected]>
Co-authored-by: ViCt0r99 <[email protected]>
Co-authored-by: ViCt0r99 <[email protected]>
Co-authored-by: Wojtek Siudzinski <[email protected]>
  • Loading branch information
5 people authored Apr 16, 2023
1 parent 6dcd0c7 commit 770fd1b
Show file tree
Hide file tree
Showing 20 changed files with 230 additions and 169 deletions.
1 change: 1 addition & 0 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- 'develop'
- 'feature/**'
- 'fix/**'

jobs:
lint:
Expand Down
2 changes: 1 addition & 1 deletion py_ocpi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Python Implementation of OCPI"""

__version__ = "0.3.0"
__version__ = "0.3.1"

from .core import enums, data_types
from .main import get_application
6 changes: 5 additions & 1 deletion py_ocpi/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ def get_versions():
return [
Version(
version=VersionNumber.v_2_2_1,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/{VersionNumber.v_2_2_1}/details')
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/{VersionNumber.v_2_2_1.value}/details')
).dict(),
]


def get_endpoints():
return {}


def pagination_filters(
date_from: datetime = Query(default=None),
date_to: datetime = Query(default=datetime.now()),
Expand Down
195 changes: 98 additions & 97 deletions py_ocpi/core/endpoints.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,108 @@
from py_ocpi.core.enums import ModuleID
from py_ocpi.core.enums import ModuleID, RoleEnum
from py_ocpi.core.data_types import URL
from py_ocpi.core.config import settings
from py_ocpi.modules.versions.schemas import Endpoint
from py_ocpi.modules.versions.enums import VersionNumber, InterfaceRole

ENDPOINTS = {
VersionNumber.v_2_2_1: [
VersionNumber.v_2_2_1: {
# ###############--CPO--###############

# locations
Endpoint(
identifier=ModuleID.locations,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.locations}')
),
# sessions
Endpoint(
identifier=ModuleID.sessions,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.sessions}')
),
# credentials
Endpoint(
identifier=ModuleID.credentials_and_registration,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.credentials_and_registration}')
),
# tariffs
Endpoint(
identifier=ModuleID.tariffs,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.tariffs}')
),
# cdrs
Endpoint(
identifier=ModuleID.cdrs,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.cdrs}')
),
# tokens
Endpoint(
identifier=ModuleID.tokens,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1}/{ModuleID.tokens}')
),
RoleEnum.cpo: [
# locations
Endpoint(
identifier=ModuleID.locations,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.locations.value}')
),
# sessions
Endpoint(
identifier=ModuleID.sessions,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.sessions.value}')
),
# credentials
Endpoint(
identifier=ModuleID.credentials_and_registration,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.credentials_and_registration.value}')
),
# tariffs
Endpoint(
identifier=ModuleID.tariffs,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.tariffs.value}')
),
# cdrs
Endpoint(
identifier=ModuleID.cdrs,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.cdrs.value}')
),
# tokens
Endpoint(
identifier=ModuleID.tokens,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.tokens.value}')
),
],

# ###############--EMSP--###############

# credentials
Endpoint(
identifier=ModuleID.credentials_and_registration,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.credentials_and_registration}')
),
# locations
Endpoint(
identifier=ModuleID.locations,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.locations}')
),
# sessions
Endpoint(
identifier=ModuleID.sessions,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.sessions}')
),
# cdrs
Endpoint(
identifier=ModuleID.cdrs,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.cdrs}')
),
# tariffs
Endpoint(
identifier=ModuleID.tariffs,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.tariffs}')
),
# commands
Endpoint(
identifier=ModuleID.commands,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.commands}')
),
# tokens
Endpoint(
identifier=ModuleID.tokens,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1}/{ModuleID.tokens}')
),
]

RoleEnum.emsp: [
# credentials
Endpoint(
identifier=ModuleID.credentials_and_registration,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.credentials_and_registration.value}')
),
# locations
Endpoint(
identifier=ModuleID.locations,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.locations.value}')
),
# sessions
Endpoint(
identifier=ModuleID.sessions,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.sessions.value}')
),
# cdrs
Endpoint(
identifier=ModuleID.cdrs,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.cdrs.value}')
),
# tariffs
Endpoint(
identifier=ModuleID.tariffs,
role=InterfaceRole.receiver,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.tariffs.value}')
),
# commands
Endpoint(
identifier=ModuleID.commands,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.commands.value}')
),
# tokens
Endpoint(
identifier=ModuleID.tokens,
role=InterfaceRole.sender,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp'
f'/{VersionNumber.v_2_2_1.value}/{ModuleID.tokens.value}')
),
]
}
}
4 changes: 2 additions & 2 deletions py_ocpi/core/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from py_ocpi.core.adapter import Adapter
from py_ocpi.core.crud import Crud
from py_ocpi.core.schemas import Push, PushResponse, ReceiverResponse
from py_ocpi.core.utils import get_auth_token
from py_ocpi.core.utils import encode_string_base64, get_auth_token
from py_ocpi.core.dependencies import get_crud, get_adapter
from py_ocpi.core.enums import ModuleID, RoleEnum
from py_ocpi.core.config import settings
Expand Down Expand Up @@ -66,7 +66,7 @@ async def push_object(version: VersionNumber, push: Push, crud: Crud, adapter: A
receiver_responses = []
for receiver in push.receivers:
# get client endpoints
client_auth_token = f'Token {receiver.auth_token}'
client_auth_token = f'Token {encode_string_base64(receiver.auth_token)}'
async with httpx.AsyncClient() as client:
response = await client.get(receiver.endpoints_url,
headers={'authorization': client_auth_token})
Expand Down
4 changes: 2 additions & 2 deletions py_ocpi/core/schemas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime, timezone
from typing import List
from typing import List, Union

from pydantic import BaseModel

Expand All @@ -11,7 +11,7 @@ class OCPIResponse(BaseModel):
"""
https://github.com/ocpi/ocpi/blob/2.2.1/transport_and_format.asciidoc#117-response-format
"""
data: list
data: Union[list, dict]
status_code: int
status_message: String(255)
timestamp: DateTime = str(datetime.now(timezone.utc))
Expand Down
16 changes: 15 additions & 1 deletion py_ocpi/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import urllib
import base64

from fastapi import Response, Request
from pydantic import BaseModel
Expand All @@ -18,7 +19,10 @@ def set_pagination_headers(response: Response, link: str, total: int, limit: int
def get_auth_token(request: Request) -> str:
headers = request.headers
headers_token = headers.get('authorization', 'Token Null')
return headers_token.split()[1]
token = headers_token.split()[1]
if token == 'Null': # nosec
return None
return decode_string_base64(token)


async def get_list(response: Response, filters: dict, module: ModuleID, role: RoleEnum,
Expand All @@ -40,3 +44,13 @@ async def get_list(response: Response, filters: dict, module: ModuleID, role: Ro
def partially_update_attributes(instance: BaseModel, attributes: dict):
for key, value in attributes.items():
setattr(instance, key, value)


def encode_string_base64(input: str) -> str:
input_bytes = base64.b64encode(bytes(input, 'utf-8'))
return input_bytes.decode('utf-8')


def decode_string_base64(input: str) -> str:
input_bytes = base64.b64decode(bytes(input, 'utf-8'))
return input_bytes.decode('utf-8')
38 changes: 21 additions & 17 deletions py_ocpi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from fastapi.middleware.cors import CORSMiddleware
from pydantic import ValidationError
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from py_ocpi.core.endpoints import ENDPOINTS

from py_ocpi.modules.versions.api import router as versions_router, versions_v_2_2_1_router
from py_ocpi.modules.versions.enums import VersionNumber
from py_ocpi.modules.versions.schemas import Version
from py_ocpi.core.dependencies import get_crud, get_adapter, get_versions
from py_ocpi.core.dependencies import get_crud, get_adapter, get_versions, get_endpoints
from py_ocpi.core import status
from py_ocpi.core.enums import RoleEnum
from py_ocpi.core.config import settings
Expand Down Expand Up @@ -82,40 +83,38 @@ def get_application(
)

versions = []
version_endpoints = {}

if VersionNumber.v_2_2_1 in version_numbers:
_app.include_router(
versions_v_2_2_1_router,
prefix=f'/{settings.OCPI_PREFIX}',
)

versions.append(
Version(
version=VersionNumber.v_2_2_1,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/{VersionNumber.v_2_2_1.value}/details')
).dict(),
)

version_endpoints[VersionNumber.v_2_2_1] = []

if RoleEnum.cpo in roles:
_app.include_router(
v_2_2_1_cpo_router,
prefix=f'/{settings.OCPI_PREFIX}/cpo/{VersionNumber.v_2_2_1}',
prefix=f'/{settings.OCPI_PREFIX}/cpo/{VersionNumber.v_2_2_1.value}',
tags=['CPO']
)

versions.append(
Version(
version=VersionNumber.v_2_2_1,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/cpo/{VersionNumber.v_2_2_1}')
).dict(),
)
version_endpoints[VersionNumber.v_2_2_1] += ENDPOINTS[VersionNumber.v_2_2_1][RoleEnum.cpo]

if RoleEnum.emsp in roles:
_app.include_router(
v_2_2_1_emsp_router,
prefix=f'/{settings.OCPI_PREFIX}/emsp/{VersionNumber.v_2_2_1}',
prefix=f'/{settings.OCPI_PREFIX}/emsp/{VersionNumber.v_2_2_1.value}',
tags=['EMSP']
)

versions.append(
Version(
version=VersionNumber.v_2_2_1,
url=URL(f'https://{settings.OCPI_HOST}/{settings.OCPI_PREFIX}/emsp/{VersionNumber.v_2_2_1}')
).dict(),
)
version_endpoints[VersionNumber.v_2_2_1] += ENDPOINTS[VersionNumber.v_2_2_1][RoleEnum.emsp]

def override_get_crud():
return crud
Expand All @@ -132,4 +131,9 @@ def override_get_versions():

_app.dependency_overrides[get_versions] = override_get_versions

def override_get_endpoints():
return version_endpoints

_app.dependency_overrides[get_endpoints] = override_get_endpoints

return _app
2 changes: 1 addition & 1 deletion py_ocpi/modules/cdrs/v_2_2_1/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ChargingPeriod(BaseModel):
https://github.com/ocpi/ocpi/blob/2.2.1/mod_cdrs.asciidoc#146-chargingperiod-class
"""
start_date_time: DateTime
diemnsions: List[CdrDimension]
dimensions: List[CdrDimension]
tariff_id: Optional[CiString(36)]


Expand Down
Loading

0 comments on commit 770fd1b

Please sign in to comment.