diff --git a/.github/workflows/data-consolidation.yml b/.github/workflows/data-consolidation.yml new file mode 100644 index 0000000..1e2ae9a --- /dev/null +++ b/.github/workflows/data-consolidation.yml @@ -0,0 +1,53 @@ +# name: databuilder + +# on: +# push: +# branches: +# - main + +# jobs: +# run-workflow-script: +# runs-on: ubuntu-latest + +# steps: +# - name: Checkout main branch +# uses: actions/checkout@v2 +# with: +# ref: main + +# - name: Set up Python +# uses: actions/setup-python@v2 +# with: +# python-version: "3.11.4" + +# # - name: Install dependencies # not needed atm as we are not using any requirements apart from default libs +# # run: | +# # python -m pip install --upgrade pip +# # pip install -r requirements.txt + +# - name: Run Python script +# run: python src/combine_data.py + +# - name: Archive generated files as artifact +# uses: actions/upload-artifact@v4 +# with: +# name: generated-files +# path: ./api + +# - name: Checkout production branch +# uses: actions/checkout@v2 +# with: +# ref: production + +# - name: Download generated files artifact +# uses: actions/download-artifact@v4 +# with: +# name: generated-files +# path: ./api + +# - name: Apply changes to production branch +# run: | +# git config --local user.email "action@github.com" +# git config --local user.name "GitHub Action" +# git add . +# git diff-index --quiet HEAD || (git commit -a -m "Add generated files from main branch" --allow-empty && git push origin production) diff --git a/.github/workflows/data-sorting.yml b/.github/workflows/data-sorting.yml new file mode 100644 index 0000000..9c73352 --- /dev/null +++ b/.github/workflows/data-sorting.yml @@ -0,0 +1,24 @@ +# name: data sorter +# run-name: Sorting data on branch ${{github.ref_name}} +# on: +# push: +# branches: +# - "!main" +# - "!production" +# - "*" + +# permissions: write-all +# jobs: +# sort-data: +# runs-on: ubuntu-latest +# steps: +# - name: checkout repo content +# uses: actions/checkout@v2 + +# - name: setup python +# uses: actions/setup-python@v4 +# with: +# python-version: "3.11" + +# - name: sort data +# run: python src/sort_data.py diff --git a/.github/workflows/data-validation.yml b/.github/workflows/data-validation.yml new file mode 100644 index 0000000..53c407f --- /dev/null +++ b/.github/workflows/data-validation.yml @@ -0,0 +1,24 @@ +# name: data validator +# run-name: Validating data on branch ${{github.ref_name}} +# on: +# push: +# branches: +# - "!main" +# - "!production" +# - "*" + +# permissions: write-all +# jobs: +# sort-data: +# runs-on: ubuntu-latest +# steps: +# - name: checkout repo content +# uses: actions/checkout@v2 + +# - name: setup python +# uses: actions/setup-python@v4 +# with: +# python-version: "3.11" + +# - name: sort data +# run: python src/validate_data.py diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 10e6497..fb4a1c2 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,47 +1,47 @@ -name: data-sorter -run-name: Push by ${{ github.actor }} is sorting station mappings.json -on: - push: - branches: - - "*" - - "*/**" - - "!main" # Exclude the main branch -permissions: write-all -jobs: - consolidate-and-sort-data: - runs-on: ubuntu-latest - steps: - - name: checkout repo content - uses: actions/checkout@v2 # checkout the repository content +# name: data-sorter +# run-name: Push by ${{ github.actor }} is sorting station mappings.json +# on: +# push: +# branches: +# - "*" +# - "*/**" +# - "!main" # Exclude the main branch +# permissions: write-all +# jobs: +# consolidate-and-sort-data: +# runs-on: ubuntu-latest +# steps: +# - name: checkout repo content +# uses: actions/checkout@v2 # checkout the repository content - - name: setup python - uses: actions/setup-python@v4 - with: - python-version: "3.11" # install the python version needed +# - name: setup python +# uses: actions/setup-python@v4 +# with: +# python-version: "3.11" # install the python version needed - - name: Check, sort and consolidate data - id: consolidate - working-directory: . - run: python .github/workflows/main.py +# - name: Check, sort and consolidate data +# id: consolidate +# working-directory: . +# run: python .github/workflows/main.py - - name: Check script outcome - run: | - if [ ${{ steps.consolidate.outcome }} != 'success' ]; then - echo "Sorting and consolidation of data failed, data invalid" - exit 1 - fi +# - name: Check script outcome +# run: | +# if [ ${{ steps.consolidate.outcome }} != 'success' ]; then +# echo "Sorting and consolidation of data failed, data invalid" +# exit 1 +# fi - - if: ${{ steps.consolidate.outcome == 'success' }} - name: commit files - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add -A - git diff-index --quiet HEAD || (git commit -a -m "Consolidated and formatted data" --allow-empty) +# - if: ${{ steps.consolidate.outcome == 'success' }} +# name: commit files +# run: | +# git config --local user.email "action@github.com" +# git config --local user.name "GitHub Action" +# git add -A +# git diff-index --quiet HEAD || (git commit -a -m "Consolidated and formatted data" --allow-empty) - - if: ${{ steps.consolidate.outcome == 'success' }} - name: push changes - uses: ad-m/github-push-action@v0.6.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.ref }} +# - if: ${{ steps.consolidate.outcome == 'success' }} +# name: push changes +# uses: ad-m/github-push-action@v0.6.0 +# with: +# github_token: ${{ secrets.GITHUB_TOKEN }} +# branch: ${{ github.ref }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5fd060b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run tests", + "type": "shell", + "command": "python", + "args": [ + "src/test_runner.py" + ] + } + ] + } \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..24faae0 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- read data and overwrite data later on, to automatically enforce upper case for all ICAO for ex. or convert str frequencies to float diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sort_data.py b/src/sort_data.py new file mode 100644 index 0000000..56b49aa --- /dev/null +++ b/src/sort_data.py @@ -0,0 +1 @@ +print("Test sort workflow") diff --git a/src/test_runner.py b/src/test_runner.py new file mode 100644 index 0000000..6d4637b --- /dev/null +++ b/src/test_runner.py @@ -0,0 +1,31 @@ +import unittest +import os +import sys + + +def run_tests(): + """ + This function runs all unit tests inside of the tests/ directory and its subdirectories. + """ + + # Get the absolute path of the directory containing this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # Construct the path to the tests/ directory next to this script + tests_dir = os.path.join(script_dir, "tests") + + # Discover and run tests in the tests/ directory and its subdirectories + test_loader = unittest.TestLoader() + test_suite = test_loader.discover( + start_dir=tests_dir, pattern="test_*.py", top_level_dir=script_dir + ) + test_runner = unittest.TextTestRunner() + result = test_runner.run(test_suite) + + # Explicitly exit with status code based on test result + if not result.wasSuccessful(): + sys.exit(1) # Exit with non-zero status code if tests failed + + +if __name__ == "__main__": + run_tests() diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/validators/__init__.py b/src/tests/validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/validators/test_validator.py b/src/tests/validators/test_validator.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/views/__init__.py b/src/tests/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/views/test_icao.py b/src/tests/views/test_icao.py new file mode 100644 index 0000000..29a7755 --- /dev/null +++ b/src/tests/views/test_icao.py @@ -0,0 +1,29 @@ +from unittest import TestCase + +from pydantic import ValidationError +from views.icao import ICAOModel + + +class TestICAOType(TestCase): + def test_normal(self): + ICAOModel(icao="EDDB") + ICAOModel(icao="EDDL") + ICAOModel(icao="EDDF") + + def test_lowercase(self): + test = ICAOModel(icao="eddl") + + self.assertEqual(test.icao, "EDDL") + + def test_expections(self): + # ValidationError due to numbers in ICAO + with self.assertRaises(ValidationError): + ICAOModel(icao="1ADW") + + # ValidationError due <4 letters + with self.assertRaises(ValidationError): + ICAOModel(icao="EDD") + + # ValidationError due >4 letters + with self.assertRaises(ValidationError): + ICAOModel(icao="EDDTT") diff --git a/src/validate_data.py b/src/validate_data.py new file mode 100644 index 0000000..242beb6 --- /dev/null +++ b/src/validate_data.py @@ -0,0 +1 @@ +print("Test validate workflow") diff --git a/src/validators/frequency.py b/src/validators/frequency.py new file mode 100644 index 0000000..e69de29 diff --git a/src/validators/logon.py b/src/validators/logon.py new file mode 100644 index 0000000..2b0c9fb --- /dev/null +++ b/src/validators/logon.py @@ -0,0 +1,34 @@ +from pydantic import ValidationError + +LOGON_SUFFIXES = [ + "DEL", + "RMP", + "GND", + "TWR", + "DEP", + "APP", + "CTR", + "FSS", + "RDO", + "TMV", + "FMP", +] + + +def logon_validator(value: str) -> str: + value = value.strip() + + parts = value.split("_") + + if not parts[-1] in LOGON_SUFFIXES: + raise ValueError(f"Suffix {parts[-1]} is not allowed.") + + # Ensure logon is exactly 5 alphanumeric characters + if len(value) != 5: + raise ValueError("Logon must be exactly 5 characters long.") + + if not value.isalnum(): + raise ValueError("Logon must contain only alphanumeric characters.") + + # Return the cleaned value (you could also enforce uppercase or other formatting) + return value.upper() diff --git a/src/views/__init__.py b/src/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/views/icao.py b/src/views/icao.py new file mode 100644 index 0000000..4f143e5 --- /dev/null +++ b/src/views/icao.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel, Field, field_validator + + +class ICAOModel(BaseModel): + icao: str = Field( + ..., + pattern=r"^[A-Za-z]{4}$", + description="Must be a string of exactly 4 letters.", + ) + + @field_validator("icao") + @classmethod + def validate_icao(cls, value: str) -> str: + return value.strip().upper() diff --git a/src/views/schedules.py b/src/views/schedules.py new file mode 100644 index 0000000..f3497a6 --- /dev/null +++ b/src/views/schedules.py @@ -0,0 +1,4 @@ +from typing import Literal + + +ScheduleType = Literal["EDGG", "EDMM", "EDWW"] diff --git a/src/views/station.py b/src/views/station.py new file mode 100644 index 0000000..51f02ce --- /dev/null +++ b/src/views/station.py @@ -0,0 +1,22 @@ +from typing import List, Literal, Optional +from pydantic import BaseModel, field_validator + +from validators.logon import logon_validator +from views.schedules import ScheduleType + + +class Station(BaseModel): + logon: str + frequency: float + abbreviation: str + description: Optional[str] + schedule_show_always: Optional[ScheduleType] + schedule_show_booked: Optional[ScheduleType] + relevant_airports: List[str] + gcap_status: Optional[Literal["AFIS", "1", "2"]] + s1_twr: Optional[bool] + + @field_validator("logon") + @classmethod + def validate_logon(cls, value: str) -> str: + return logon_validator(value)