Skip to content

Commit

Permalink
feat: socket server as a service
Browse files Browse the repository at this point in the history
  • Loading branch information
tristiisch committed Sep 26, 2024
1 parent dea30d8 commit 0ded3f3
Show file tree
Hide file tree
Showing 29 changed files with 266 additions and 204 deletions.
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@
}
],
"justMyCode": false
},
{
"name": "Python: Remote Attach CLI",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5679
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
],
"justMyCode": false
}
]
}
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ LABEL org.opencontainers.image.source="https://github.com/tristiisch/PyRamid" \
org.opencontainers.image.authors="tristiisch" \
version="$PROJECT_VERSION"

HEALTHCHECK --interval=30s --retries=3 --timeout=30s CMD python ./src/cli.py health
# HEALTHCHECK --interval=30s --retries=3 --timeout=30s CMD python ./src/startup_cli.py health
# Expose port for health check
EXPOSE 49150

Expand Down Expand Up @@ -101,14 +101,16 @@ FROM base AS executable-dev
ARG APP_USER
ARG APP_GROUP

# HEALTHCHECK --interval=30s --retries=3 --timeout=30s CMD python -Xfrozen_modules=off ./src/startup_cli_dev.py health

COPY --chown=root:$APP_GROUP --chmod=550 --from=builder-dev /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

COPY --chown=root:$APP_GROUP --chmod=750 ./src ./src

USER $APP_USER

CMD ["python", "-Xfrozen_modules=off", "./src/dev.py"]
CMD ["python", "-Xfrozen_modules=off", "./src/startup_dev.py"]

# ============================ Test Image ============================
FROM base AS tests
Expand Down
16 changes: 14 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DOCKER_COMPOSE_FILE_PREPROD := docker-compose.preprod.yml
DOCKER_SERVICE_PREPROD := pyramid_preprod_pyramid
DOCKER_CONTEXT_PREPROD := cookie-pulsheberg

.PHONY: logs
# Basics

all: up-b logs

Expand Down Expand Up @@ -45,6 +45,8 @@ logs:
exec:
@docker compose exec $(COMPOSE_SERVICE) sh

# Other envs

exec-pp:
@scripts/docker_service_exec.sh $(DOCKER_SERVICE_PREPROD) $(DOCKER_CONTEXT_PREPROD)

Expand All @@ -56,6 +58,14 @@ tests:
@mkdir -p ./cover && chmod 777 ./cover
@docker run --rm --env-file ./.env -v ./cover:/app/cover pyramid:tests

healthcheck:
@docker compose exec $(COMPOSE_SERVICE) sh -c "python ./src/startup_cli.py health"

healthcheck-dev:
@docker compose exec $(COMPOSE_SERVICE) sh -c "python -Xfrozen_modules=off ./src/startup_cli_dev.py health"

# Pythons scripts

img-b:
@python scripts/environnement.py --build

Expand All @@ -68,4 +78,6 @@ img-c:
clean:
@python scripts/environnement.py --clean

.PHONY: build tests
# Other

.PHONY: build tests logs
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ services:
- pyramid_network
ports:
- 5678:5678
- 5679:5679
env_file: .env

networks:
Expand Down
1 change: 0 additions & 1 deletion src/pyramid/api/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
from .discord import IDiscordService
from .information import IInformationService
from .logger import ILoggerService
from .socket_server import ISocketServerService
# from .source_service import ISourceService
12 changes: 8 additions & 4 deletions src/pyramid/api/services/socket_server.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from abc import ABC, abstractmethod
from pyramid.data.functional.application_info import ApplicationInfo
from abc import abstractmethod

class ISocketServerService(ABC):

class ISocketServerService:

@abstractmethod
async def open(self):
pass

@abstractmethod
def start(self):
def close(self):
pass
13 changes: 7 additions & 6 deletions src/pyramid/api/services/tools/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ def import_services(cls):
importlib.import_module(full_module_name)

@classmethod
def register_service(cls, name: str, type: type[object]):
def register_service(cls, interface_name: str, type: type[object]):
type_name = type.__name__
if not issubclass(type, ServiceInjector):
raise TypeError("Service %s is not a subclass of ServiceInjector and cannot be initialized." % name)
if name in cls.__SERVICES_REGISTRED:
already_class_name = cls.__SERVICES_REGISTRED[name].__name__
raise TypeError("Service %s is not a subclass of ServiceInjector and cannot be initialized." % type_name)
if interface_name in cls.__SERVICES_REGISTRED:
already_class_name = cls.__SERVICES_REGISTRED[interface_name].__name__
raise ServiceAlreadyRegisterException(
"Cannot register %s with %s, it is already registered with the class %s."
% (name, type.__name__, already_class_name)
% (interface_name, type_name, already_class_name)
)
cls.__SERVICES_REGISTRED[name] = type
cls.__SERVICES_REGISTRED[interface_name] = type

@classmethod
def create_services(cls):
Expand Down
1 change: 1 addition & 0 deletions src/pyramid/api/tasks/tools/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ def running(loop: asyncio.AbstractEventLoop):
parameters.thread.join()

signal.signal(signal.SIGTERM, previous_handler)
logging.info("All tasks are stopped")
12 changes: 6 additions & 6 deletions src/cli.py → src/pyramid/cli/startup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import argparse
import sys

from pyramid.api.services.information import IInformationService
from pyramid.api.services.tools.tester import ServiceStandalone
from pyramid.client.client import SocketClient
from pyramid.client.requests.health import HealthRequest
from pyramid.client.requests.ping import PingRequest

def startup_cli():
ServiceStandalone.import_services()
Expand All @@ -29,11 +30,10 @@ def startup_cli():

elif args.health:
sc = SocketClient(args.host, args.port)
health = HealthRequest()
sc.send(health)
health = PingRequest()
result = sc.send(health)
if result is not True:
sys.exit(1)

else:
parser.print_help()

if __name__ == "__main__":
startup_cli()
16 changes: 8 additions & 8 deletions src/pyramid/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,24 @@ def send(self, req: ARequest) -> bool:

try:
# Convert the request data to JSON
json_request = self.__common.serialize(req.ask)
json_request = SocketCommon.serialize(req.ask)

# Connect to the server
self.__logger.info("Connect to %s:%d", self.__host, self.__port)
client_socket.connect((self.__host, self.__port))

# Send the JSON request to the server
self.__logger.debug("Send '%s'", json_request)
self.__common.send_chunk(client_socket, json_request)
SocketCommon.send_chunk(client_socket, json_request)

# Receive the response from the server
response_str = self.__common.receive_chunk(client_socket)
if not response_str:
self.__logger.warning("Received empty request")
return False

self.__logger.debug("Received '%s'", response_str)
self.receive(req, response_str)
return True
result = self.receive(req, response_str)
return result

except OverflowError:
self.__logger.warning(
Expand All @@ -67,13 +66,14 @@ def send(self, req: ARequest) -> bool:
client_socket.close()
return False

def receive(self, action: ARequest, response_str: str):
response: SocketResponse = SocketResponse.from_json(self.__common.deserialize(response_str))
def receive(self, action: ARequest, response_str: str) -> bool:
response: SocketResponse = SocketResponse.from_str(response_str)

if not response.header:
raise ValueError("No header received")
if not response.data:
raise ValueError("No data received")

response_data = action.load_data(**(self.__common.deserialize(response.data)))
action.client_receive(response.header, response_data)
result = action.client_receive(response.header, response_data)
return result
9 changes: 6 additions & 3 deletions src/pyramid/client/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ def receive_chunk(self, client_socket: sock):
return None
return received_data.decode("utf-8")

def send_chunk(self, client_socket: sock, response: str):
@classmethod
def send_chunk(cls, client_socket: sock, response: str):
# for chunk in [
# response[i : i + self.buffer_size] for i in range(0, len(response), self.buffer_size)
# ]:
# client_socket.send(chunk.encode("utf-8"))
client_socket.send(response.encode("utf-8"))

def serialize(self, obj):
@classmethod
def serialize(cls, obj):
def default(obj):
if hasattr(obj, "__dict__"):
# if isinstance(obj, SocketResponse):
Expand All @@ -46,7 +48,8 @@ def default(obj):

return json.dumps(obj, default=default)

def deserialize(self, obj: str, object_hook=None):
@classmethod
def deserialize(cls, obj: str, object_hook=None):
return json.loads(obj, object_hook=object_hook)


Expand Down
4 changes: 2 additions & 2 deletions src/pyramid/client/requests/a_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from typing import Any

# from pyramid.client.common import SocketHeader
from pyramid.client.responses.a_response import ResponseHeader
from pyramid.client.a_socket import ASocket
from pyramid.client.requests.ask_request import AskRequest
from pyramid.client.responses.a_response_header import ResponseHeader


class ARequest(ASocket):
Expand All @@ -17,5 +17,5 @@ def load_data(self, data) -> Any:
pass

@abstractmethod
def client_receive(self, header: ResponseHeader, data: Any):
def client_receive(self, header: ResponseHeader, data: Any) -> bool:
pass
27 changes: 0 additions & 27 deletions src/pyramid/client/requests/health.py

This file was deleted.

26 changes: 26 additions & 0 deletions src/pyramid/client/requests/ping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import json
import logging
import sys
from pyramid.client.requests.a_request import ARequest
from pyramid.client.requests.ask_request import AskRequest
from pyramid.client.responses.a_response_header import ResponseHeader
from pyramid.data.ping import PingSocket


class PingRequest(ARequest):
def __init__(self) -> None:
super().__init__(AskRequest("health"))

def load_data(self, **data) -> PingSocket:
return PingSocket(**data)

def client_receive(self, header: ResponseHeader, data: PingSocket) -> bool:
data_json = json.dumps(data.__dict__, indent=4)

if not data.is_ok():
logging.warning("Health check failed")
print(data_json)
return False
logging.info("Health check valid")
print(data_json)
return True
17 changes: 8 additions & 9 deletions src/pyramid/client/responses/a_response.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
from typing import Any, Self
from pyramid.client.a_socket import ASocket
from pyramid.client.common import ResponseCode
from pyramid.client.common import ResponseCode, SocketCommon
from pyramid.client.responses.a_response_header import ResponseHeader
# from pyramid.client.common import ResponseCode, SocketHeader


# class ReponseHeader(SocketHeader):
class ResponseHeader:
def __init__(self, code: ResponseCode, message: str | None) -> None:
# super().__init__(self.__class__)
self.code = code
self.message = message


class SocketResponse(ASocket):
def __init__(
self,
Expand Down Expand Up @@ -45,6 +38,12 @@ def to_json(self, serializer):
"error_data": serializer(self.error_data) if self.error_data else None,
}

@classmethod
def from_str(cls, data: str) -> Self:
json = SocketCommon.deserialize(data)
self = cls.from_json(json)
return self

@classmethod
def from_json(cls, json_dict: dict) -> Self:
self: Self = cls()
Expand Down
12 changes: 12 additions & 0 deletions src/pyramid/client/responses/a_response_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import Any, Self
from pyramid.client.a_socket import ASocket
from pyramid.client.common import ResponseCode, SocketCommon
# from pyramid.client.common import ResponseCode, SocketHeader


# class ReponseHeader(SocketHeader):
class ResponseHeader:
def __init__(self, code: ResponseCode, message: str | None) -> None:
# super().__init__(self.__class__)
self.code = code
self.message = message
Loading

0 comments on commit 0ded3f3

Please sign in to comment.