Arkadiusz Ryś преди 1 година
родител
ревизия
b6aeff41e9
променени са 17 файла, в които са добавени 339 реда и са изтрити 0 реда
  1. 10 0
      .dockerignore
  2. 18 0
      .editorconfig
  3. 8 0
      .gitignore
  4. 81 0
      .gitlab-ci.yml
  5. 10 0
      Dockerfile
  6. 7 0
      HISTORY.rst
  7. 22 0
      LICENSE
  8. 3 0
      data/configuration.toml
  9. 46 0
      docs/templates/pyproject.toml
  10. BIN
      docs/whiteboard.jpeg
  11. 3 0
      mocka/__init__.py
  12. 23 0
      mocka/__main__.py
  13. 12 0
      mocka/configuration.py
  14. 39 0
      mocka/endpoint.py
  15. 15 0
      mocka/main.py
  16. 18 0
      mocka/router.py
  17. 24 0
      requirements.txt

+ 10 - 0
.dockerignore

@@ -0,0 +1,10 @@
+__pycache__/
+*$py.class
+/.git
+/.idea
+/docs
+/data
+/.dockerignore
+/.gitignore
+/.gitlab-ci.yml
+/README.md

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{css,html,yml,yaml,js,xml,nix}]
+indent_size = 2
+
+[{*.log,LICENSE}]
+insert_final_newline = false
+
+[*.rst]
+indent_size = 3

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+__pycache__/
+venv/
+build/
+dist/
+*.egg-info
+*.log
+
+.idea

+ 81 - 0
.gitlab-ci.yml

@@ -0,0 +1,81 @@
+image: docker:23.0.5
+
+variables:
+  DOCKER_DRIVER: overlay2
+  DOCKER_TLS_CERTDIR: "/certs"
+  DOCKER_HOST: tcp://docker:2376
+  # Where to publish this build's tagged working container.
+  DOCKER_SHA: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
+  DOCKER_BRANCH: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
+  DOCKER_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
+  DOCKER_LATEST: $CI_REGISTRY_IMAGE:latest
+
+services:
+  - docker:23.0.5-dind
+
+stages:
+  - build
+  - test
+  - release
+
+before_script:
+  - docker info
+  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+
+build-upload:
+  image: python:3.11
+  stage: build
+  before_script:
+    - python -V
+    - echo "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"
+  script:
+    - pip install build twine flit
+    - FLIT_INDEX_URL=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi flit build
+    - TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*
+  rules:
+    - if: $CI_COMMIT_TAG
+    - if: $CI_PIPELINE_SOURCE == "push" || $CI_COMMIT_BRANCH == "dev"
+      when: manual
+      allow_failure: true
+
+.build-upload-pypi:
+  image: python:3.11
+  stage: build
+  before_script:
+    - python -V
+    - echo "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"
+  script:
+    - pip install build twine flit
+    - flit build
+    - python -m twine upload dist/*
+  rules:
+    - if: $CI_COMMIT_TAG
+    - if: $CI_PIPELINE_SOURCE == "push" || $CI_COMMIT_BRANCH == "dev"
+      when: manual
+      allow_failure: true
+
+build-docker:
+  stage: build
+  script:
+    - docker pull $DOCKER_BRANCH || true
+    - docker build --cache-from $DOCKER_BRANCH -f ./Dockerfile --tag $DOCKER_BRANCH .
+    - docker push $DOCKER_BRANCH
+  rules:
+    - if: $CI_COMMIT_TAG || $CI_COMMIT_BRANCH == "main"
+    - if: $CI_PIPELINE_SOURCE == "push" || $CI_COMMIT_BRANCH == "dev"
+      when: manual
+      allow_failure: true
+
+build-tagged-docker:
+  stage: build
+  script:
+    - docker pull $DOCKER_BRANCH || true
+    - docker tag $DOCKER_BRANCH $DOCKER_LATEST
+    - docker push $DOCKER_LATEST
+    - docker tag $DOCKER_BRANCH $DOCKER_TAG
+    - docker push $DOCKER_TAG
+  rules:
+    - if: $CI_COMMIT_TAG
+    - if: $CI_PIPELINE_SOURCE == "push" || $CI_COMMIT_BRANCH == "dev"
+      when: manual
+      allow_failure: true

+ 10 - 0
Dockerfile

@@ -0,0 +1,10 @@
+ARG PYTHON_VERSION=3.11-slim-bullseye
+FROM python:${PYTHON_VERSION}
+ARG APP_HOME=/app
+ENV PYTHONUNBUFFERED 1
+ENV PYTHONDONTWRITEBYTECODE 1
+WORKDIR ${APP_HOME}
+COPY ./requirements.txt ${APP_HOME}/requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . ${APP_HOME}
+CMD ["python3", "-m", "mocka"]

+ 7 - 0
HISTORY.rst

@@ -0,0 +1,7 @@
+=======
+History
+=======
+
+0.0.0 (yyyy-mm-dd)
+------------------
+* No history yet.

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2023, Arkadiusz Michał Ryś
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 3 - 0
data/configuration.toml

@@ -0,0 +1,3 @@
+[server]
+host = "0.0.0.0"
+port = 7999

+ 46 - 0
docs/templates/pyproject.toml

@@ -0,0 +1,46 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "mocka"
+authors = [
+    {name = "Arkadiusz Michał Ryś", email = "Arkadiusz.Michal.Rys@gmail.com"},
+]
+readme = "README.md"
+requires-python = ">=3.9"
+classifiers = [
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3",
+    "Development Status :: 2 - Pre-Alpha",
+    "Intended Audience :: Developers",
+    "Natural Language :: English",
+]
+dynamic = ["version", "description"]
+license = {file = "LICENSE"}
+keywords = ["mocka"]
+dependencies = [
+{%- for dependency in requirements.mocka %}
+    "{{ dependency }}",
+{%- endfor %}
+]
+
+[project.optional-dependencies]
+test = [
+{%- for dependency in requirements.test %}
+    "{{ dependency }}",
+{%- endfor %}
+]
+doc = [
+{%- for dependency in requirements.doc %}
+    "{{ dependency }}",
+{%- endfor %}
+]
+dev = [
+{%- for dependency in requirements.dev %}
+    "{{ dependency }}",
+{%- endfor %}
+]
+
+[project.urls]
+source = "https://git.rys.one/dtdesign/mocka"

BIN
docs/whiteboard.jpeg


+ 3 - 0
mocka/__init__.py

@@ -0,0 +1,3 @@
+"""Mock Activity Endpoint."""
+__version__ = "0.0.0"
+__version_info__ = tuple((int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".")))

+ 23 - 0
mocka/__main__.py

@@ -0,0 +1,23 @@
+import logging
+import sys
+from pathlib import Path
+
+import arklog
+import dacite
+import toml
+import uvicorn
+
+from mocka.configuration import Configuration
+from mocka.main import get_application
+
+arklog.set_config_logging()
+
+data_dir = Path(__file__).resolve().parent.parent / Path("data")
+logging.debug(f"Looking for configuration in '{data_dir}'.")
+try:
+    configuration = toml.loads((data_dir / Path("configuration.toml")).read_text(encoding="utf-8"))
+    configuration = dacite.from_dict(data_class=Configuration, data=configuration, )
+except FileNotFoundError as e:
+    logging.error(f"Configuration 'configuration.toml' not found. {e}")
+    sys.exit(8)
+uvicorn.run(get_application(configuration), host=configuration.server.host, port=configuration.server.port)

+ 12 - 0
mocka/configuration.py

@@ -0,0 +1,12 @@
+from dataclasses import dataclass
+
+
+@dataclass(init=True, repr=True, order=False, frozen=True)
+class Server:
+    host: str
+    port: int
+
+
+@dataclass(init=True, repr=True, order=False, frozen=True)
+class Configuration:
+    server: Server

+ 39 - 0
mocka/endpoint.py

@@ -0,0 +1,39 @@
+import logging
+import arklog
+import time
+from typing import Any
+from fastapi import FastAPI, Request, Response
+from fastapi.middleware.cors import CORSMiddleware
+from mocka.router import MockRouter
+
+arklog.set_config_logging()
+
+class MockActivityEndpoint(FastAPI):
+    """"""
+
+    def __init__(self, *args: Any, title: str, description: str, version: str, configuration, **kwargs: Any):
+        """"""
+        self.title = title
+        self.description = description
+        self.version = version
+        self.configuration = configuration
+        super().__init__(*args, title=title, description=description, version=version, **kwargs)
+        logging.debug(self.description)
+        sparql_router = MockRouter(title=title, description=description, version=version, configuration=configuration)
+        self.include_router(sparql_router)
+        self.add_middleware(
+            CORSMiddleware,
+            allow_origins=["*"],
+            allow_credentials=True,
+            allow_methods=["*"],
+            allow_headers=["*"],
+        )
+
+        @self.middleware("http")
+        async def add_process_time_header(request: Request, call_next: Any) -> Response:
+            start_time = time.time()
+            response: Response = await call_next(request)
+            duration = str(time.time() - start_time)
+            response.headers["X-Process-Time"] = duration
+            logging.debug(f"X-Process-Time = {duration}")
+            return response

+ 15 - 0
mocka/main.py

@@ -0,0 +1,15 @@
+import arklog
+
+from mocka.configuration import Configuration
+from mocka.endpoint import MockActivityEndpoint
+from mocka import __version__
+
+def get_application(configuration: Configuration):
+    arklog.set_config_logging()
+    app = MockActivityEndpoint(
+        version = __version__,
+        title = "Mock Activity Endpoint",
+        description = "Mock Activity Endpoint",
+        configuration=configuration
+    )
+    return app

+ 18 - 0
mocka/router.py

@@ -0,0 +1,18 @@
+from typing import Any
+from fastapi import APIRouter, Query, Request, Response
+from fastapi.responses import JSONResponse
+
+class MockRouter(APIRouter):
+    """"""
+
+    def __init__(self, *args: Any, title: str, description: str, version: str, configuration, **kwargs: Any):
+        self.title = title
+        self.description = description
+        self.version = version
+        self.configuration = configuration
+        super().__init__(*args, **kwargs)
+
+        @self.get("/")
+        async def root_mock(request: Request, query: str | None = Query(None)) -> Response:
+            """"""
+            return JSONResponse(status_code=400, content={"message": "Not Implemented!"})

+ 24 - 0
requirements.txt

@@ -0,0 +1,24 @@
+# SpEndPoint
+toml              ~= 0.10.2
+arklog            ~= 0.5.1
+dacite            ~= 1.8.1
+fastapi           ~= 0.101.0
+pyarrow           ~= 12.0.1
+requests          ~= 2.31.0
+starlette         ~= 0.31.0
+python-magic      ~= 0.4.27
+uvicorn[standard] ~= 0.23.2
+# Test
+pytest        ~= 7.4.0
+# Doc
+sphinx ~= 7.1.2
+# Dev
+tox      ~= 4.6.4
+pip      ~= 23.2.1
+flit     ~= 3.9.0
+twine    ~= 4.0.2
+numpy    ~= 1.25.2
+invoke   ~= 2.2.0
+jinja2   ~= 3.1.2
+flake8   ~= 6.1.0
+coverage ~= 7.2.7