Skip to content

Commit 55396b6

Browse files
authored
Merge pull request #33 from ivellios/main
Walidacja i weryfikacja danych z formularza/issue w pythonie
2 parents a3dc1f8 + 9f663fe commit 55396b6

14 files changed

+626
-52
lines changed

.github/ISSUE_TEMPLATE/nowa.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ body:
8585
id: kod_pocztowy
8686
attributes:
8787
label: Kod Pocztowy
88+
description: Wprowadź kod pocztowy w formacie `00-000`
89+
placeholder: 00-000
8890
validations:
8991
required: true
9092
- type: input
@@ -97,5 +99,7 @@ body:
9799
id: telefon
98100
attributes:
99101
label: Telefon kontaktowy dla kuriera
102+
description: Wprowadź numer telefonu w formacie `000 000 000`
103+
placeholder: 000 000 000
100104
validations:
101-
required: true
105+
required: true

.github/scripts/.gitignore

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# UV
98+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99+
# This is especially recommended for binary packages to ensure reproducibility, and is more
100+
# commonly ignored for libraries.
101+
#uv.lock
102+
103+
# poetry
104+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105+
# This is especially recommended for binary packages to ensure reproducibility, and is more
106+
# commonly ignored for libraries.
107+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108+
#poetry.lock
109+
110+
# pdm
111+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112+
#pdm.lock
113+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114+
# in version control.
115+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116+
.pdm.toml
117+
.pdm-python
118+
.pdm-build/
119+
120+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121+
__pypackages__/
122+
123+
# Celery stuff
124+
celerybeat-schedule
125+
celerybeat.pid
126+
127+
# SageMath parsed files
128+
*.sage.py
129+
130+
# Environments
131+
.env
132+
.venv
133+
env/
134+
venv/
135+
ENV/
136+
env.bak/
137+
venv.bak/
138+
139+
# Spyder project settings
140+
.spyderproject
141+
.spyproject
142+
143+
# Rope project settings
144+
.ropeproject
145+
146+
# mkdocs documentation
147+
/site
148+
149+
# mypy
150+
.mypy_cache/
151+
.dmypy.json
152+
dmypy.json
153+
154+
# Pyre type checker
155+
.pyre/
156+
157+
# pytype static type analyzer
158+
.pytype/
159+
160+
# Cython debug symbols
161+
cython_debug/
162+
163+
# PyCharm
164+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166+
# and can be added to the global gitignore or merged into this file. For a more nuclear
167+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
168+
.idea/

.github/scripts/consts.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import enum
2+
3+
4+
class OrgSchemaIds(enum.StrEnum):
5+
name = "nazwa"
6+
www = "www"
7+
krs = "krs"
8+
slug = "nazwa_strony"
9+
street = "ulica"
10+
postal_code = "kod_pocztowy"
11+
city = "miasto"
12+
phone_number = "telefon"
13+
14+
NEW_ORG_ISSUE_DEFAULT_TITLE = "[Nowa Organizacja]"
15+
NEW_ORG_SCHEMA_FILENAME = "nowa.yaml"
16+
17+
18+
ORG_SCHEMA_SLUG_FIELD = "adres"

.github/scripts/labels.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import enum
2+
3+
from consts import OrgSchemaIds
4+
5+
6+
class Label(enum.StrEnum):
7+
INVALID_KRS = "niepoprawny KRS"
8+
INVALID_POSTAL_CODE = "niepoprawny kod pocztowy"
9+
INVALID_PHONE = "niepoprawny numer telefonu"
10+
INVALID_SLUG = "niepoprawna nazwa strony"
11+
AUTO_VERIFIED = "zweryfikowana automatycznie"
12+
13+
14+
INVALID_FIELD_TO_LABEL = {
15+
OrgSchemaIds.krs: Label.INVALID_KRS,
16+
OrgSchemaIds.postal_code: Label.INVALID_POSTAL_CODE,
17+
OrgSchemaIds.phone_number: Label.INVALID_PHONE,
18+
OrgSchemaIds.slug: Label.INVALID_SLUG,
19+
}

.github/scripts/parsers.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Any
2+
3+
import yaml
4+
5+
6+
class FormDataParser:
7+
8+
def __init__(self, form_data: dict[str, Any], form_schema_filename: str):
9+
self.form_data = form_data
10+
self.form_schema_filename = form_schema_filename
11+
self.form_schema = self.get_form_schema(form_schema_filename)
12+
13+
@staticmethod
14+
def get_form_schema(template_filename):
15+
with open(f"../ISSUE_TEMPLATE/{template_filename}") as f:
16+
return yaml.safe_load(f)
17+
18+
def get_label(self, identifier: str) -> str | None:
19+
for field in self.form_schema.get("body", []):
20+
if "id" in field and field["id"] == identifier:
21+
return field["attributes"]["label"]
22+
return None
23+
24+
def get(self, identifier: str) -> str | None:
25+
label = self.get_label(identifier)
26+
return self.form_data.get(label, "")

.github/scripts/process.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import json
2+
import logging
3+
import os
4+
5+
from github import Auth, Github, Issue
6+
7+
from consts import OrgSchemaIds, NEW_ORG_ISSUE_DEFAULT_TITLE, NEW_ORG_SCHEMA_FILENAME
8+
from labels import Label
9+
from parsers import FormDataParser
10+
from pullers import OrgDataPuller
11+
from utils import has_label
12+
from validators import OrgValidator
13+
14+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15+
logger = logging.getLogger(__file__)
16+
17+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
18+
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
19+
20+
auth = Auth.Token(GITHUB_TOKEN)
21+
g = Github(auth=auth)
22+
repo = g.get_repo(GITHUB_REPOSITORY)
23+
24+
25+
def process_new_org_issue(issue: Issue, data: FormDataParser):
26+
if has_label(issue, Label.AUTO_VERIFIED):
27+
issue.remove_from_labels(Label.AUTO_VERIFIED)
28+
29+
validator = OrgValidator(data, issue)
30+
if not validator.validate():
31+
logger.error("Validation failed")
32+
return
33+
34+
if not (org := OrgDataPuller.get_org_by_krs(issue, data.get(OrgSchemaIds.krs))):
35+
logger.error("KRS db validation failed")
36+
return
37+
38+
# Update issue title
39+
if issue.title == NEW_ORG_ISSUE_DEFAULT_TITLE:
40+
logger.info("Updating issue title")
41+
issue.edit(title=f"{NEW_ORG_ISSUE_DEFAULT_TITLE} {org.name or data.get(OrgSchemaIds.name)}")
42+
43+
if not has_label(issue, Label.AUTO_VERIFIED):
44+
logger.info("Adding auto-verified label")
45+
issue.create_comment(f"@{issue.user.login}, dziękujemy za podanie informacji. "
46+
f"Przyjęliśmy zgłoszenie dodania nowej organizacji. "
47+
f"Wkrótce skontaktujemy się celem weryfikacji zgłoszenia.")
48+
issue.add_to_labels(Label.AUTO_VERIFIED)
49+
50+
51+
def main():
52+
github_form_json = os.getenv("GITHUB_FORM_JSON")
53+
github_issue_number = int(os.getenv("GITHUB_ISSUE_NUMBER"))
54+
55+
issue = repo.get_issue(github_issue_number)
56+
data = FormDataParser(
57+
json.loads(github_form_json),
58+
NEW_ORG_SCHEMA_FILENAME
59+
)
60+
process_new_org_issue(issue, data)
61+
62+
63+
if __name__ == "__main__":
64+
main()

.github/scripts/pullers.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import re
2+
from typing import Optional
3+
4+
import requests
5+
from github import Issue
6+
7+
from utils import has_label
8+
from labels import Label
9+
10+
11+
class OrgDataPuller:
12+
13+
def __init__(self, krs: str):
14+
self.krs = krs
15+
self.data = self.pull_data()
16+
17+
def pull_data(self) -> dict | None:
18+
response = requests.get(f"https://api-krs.ms.gov.pl/api/krs/OdpisAktualny/{self.krs}?rejestr=S&format=json")
19+
if response.status_code == 200:
20+
return response.json()
21+
else:
22+
raise requests.HTTPError(f"Failed to fetch data for KRS {self.krs}")
23+
24+
@property
25+
def name(self):
26+
return self.data.get("odpis", {}).get("dane", {}).get("dzial1", {}).get("danePodmiotu", {}).get("nazwa")
27+
28+
@staticmethod
29+
def get_org_by_krs(issue: Issue, krs: str) -> Optional["OrgDataPuller"]:
30+
31+
# Downloading official org data
32+
try:
33+
org = OrgDataPuller(krs)
34+
except requests.HTTPError as e:
35+
issue.create_comment(f"Nie udało się pobrać danych o organizacji z KRS. "
36+
f"Proszę sprawdzić, czy podany numer jest poprawny")
37+
issue.add_to_labels(Label.INVALID_KRS)
38+
return
39+
40+
if has_label(issue, Label.INVALID_KRS):
41+
issue.remove_from_labels(Label.INVALID_KRS)
42+
43+
return org

.github/scripts/requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PyGithub==2.5.0
2+
PyYAML==6.0.2
3+
requests==2.32.3

.github/scripts/utils.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from labels import Label
2+
3+
4+
def has_label(issue, label: Label):
5+
return any([l for l in issue.labels if l.name == label])

0 commit comments

Comments
 (0)