-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/first #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
4c2551c
Project setup
ghbm-itk fe4a9e4
Implemented soap signer
ghbm-itk 4d3c5c8
Added service IndkomstOplysningPersonHent
ghbm-itk 172227f
Added docstring
ghbm-itk b6171d9
Added timeout to service call
ghbm-itk ddda4a4
Moved namespaces
ghbm-itk cb1bdeb
Formatting
ghbm-itk 086d348
Added docstring
ghbm-itk f119fe4
Lint
ghbm-itk 99ee51f
Added linters
ghbm-itk 9823a6f
Removed bla
ghbm-itk fa7e6fd
Added publish action
ghbm-itk d690281
Removed unused comment
ghbm-itk 2a00587
Added readme
ghbm-itk 8f78bc6
Typo
ghbm-itk 399f674
Update Linting.yml
ghbm-itk b220fbe
Update Linting.yml
ghbm-itk c45733a
Update Linting.yml
ghbm-itk a98ac2f
Update Linting.yml
ghbm-itk 5d49831
Update Linting.yml
ghbm-itk d13408f
Update Linting.yml
ghbm-itk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
name: Linting | ||
|
||
on: [pull_request] | ||
|
||
jobs: | ||
Linting: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: ["3.11"] | ||
fail-fast: false | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install dependencies | ||
run: | | ||
sudo apt-get update | ||
sudo apt-get install pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl | ||
python -m pip install --upgrade pip | ||
pip install uv | ||
uv pip install --system . | ||
uv pip install --system flake8 pylint | ||
|
||
|
||
- name: Analysing the code with pylint | ||
run: | | ||
pylint --rcfile=.pylintrc $(git ls-files '*.py') | ||
|
||
- name: Analysing the code with flake8 | ||
run: | | ||
flake8 --extend-ignore=E501,E251 $(git ls-files '*.py') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# This workflow will upload a Python Package using Twine when a release is created | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries | ||
|
||
# This workflow uses actions that are not certified by GitHub. | ||
# They are provided by a third-party and are governed by | ||
# separate terms of service, privacy policy, and support | ||
# documentation. | ||
|
||
name: Upload Python Package | ||
|
||
on: | ||
release: | ||
types: [published] | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python | ||
uses: actions/setup-python@v3 | ||
with: | ||
python-version: '3.x' | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install build | ||
- name: Build package | ||
run: python -m build | ||
- name: Publish package | ||
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 | ||
with: | ||
user: __token__ | ||
password: ${{ secrets.PYPI_API_TOKEN }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
.vscode/ | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[pylint.messages_control] | ||
disable = | ||
C0301, # Line too long | ||
I1101, E1101, # C-modules members | ||
R0913, # Too many arguments | ||
R0914 # Too many local variables |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.11 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,106 @@ | ||
# python-skat-webservice | ||
# python-skat-webservice | ||
|
||
This Python library is used to call the Danish SKAT eIndkomst SOAP webservice. | ||
|
||
<https://info.skat.dk/data.aspx?oid=4247> | ||
|
||
Currently the following services are supported: | ||
|
||
- IndkomstOplysningPersonHent | ||
|
||
## Prerequisites | ||
|
||
### Service agreement | ||
|
||
Before using the webservices you need to setup a service agreement with SKAT. | ||
|
||
Read more here: <https://info.skat.dk/data.aspx?oid=2248828> | ||
|
||
In the service agreement you will receive 3 codes that is used when calling the services: | ||
|
||
- AbonnentTypeKode | ||
- AbonnementTypeKode | ||
- AdgangFormaalTypeKode | ||
|
||
All three codes are numbers 3-5 digits long. | ||
|
||
### Certificates | ||
|
||
The OCES3 P12 certificate you use to register with SKAT needs to be converted to two PEM | ||
certificates before being used in the code. You can use openssl for this: | ||
|
||
```bash | ||
openssl pkcs12 -in Certificate.p12 -out Certificate.crt.pem -clcerts -nokeys | ||
openssl pkcs12 -in Certificate.p12 -out Certificate.key.pem -nocerts -nodes | ||
``` | ||
|
||
## IndkomstOplysningPersonHent | ||
|
||
### Usage | ||
|
||
The IndkomstOplysningPersonHent allows you to get income information about a single person for a given | ||
range of months. | ||
|
||
```python | ||
from python_skat_webservice.soap_signer import SOAPSigner | ||
from python_skat_webservice.common import CallerInfo | ||
from python_skat_webservice.indkomst_oplysning_person_hent import search_income | ||
|
||
if __name__ == '__main__': | ||
signer = SOAPSigner("something/certifcate.crt.pem", "something/certificate.key.pem") | ||
|
||
caller = CallerInfo( | ||
se_number="12345678", # Your company's SE number | ||
abonnent_type_kode="123", | ||
abonnement_type_kode="4567", | ||
adgang_formaal_type_kode="456", | ||
caller_id="My caller id" # Any id to identify you with SKAT. Can be anything. | ||
) | ||
|
||
result = search_income( | ||
cpr="1234567890", | ||
month_from="202401", # yyyymm | ||
month_to="202401", # yyyymm | ||
transaction_id="My transaction id", # Any id to identify the transaction. Can be anything. | ||
caller_info=caller, | ||
soap_signer=signer | ||
) | ||
|
||
print(result) | ||
``` | ||
|
||
### Output | ||
|
||
The output is a SOAP xml envelope with the search results in the body. | ||
The body is structured something like this: | ||
|
||
```text | ||
Person | ||
├── Company | ||
│ ├── Period | ||
│ │ ├── Form | ||
│ │ │ ├── Form ID | ||
│ │ │ ├── Field | ||
│ │ │ │ ├── Field ID | ||
│ │ │ │ ├── Type | ||
│ │ │ │ └── Value | ||
│ │ │ └── Field | ||
│ │ │ └── ... | ||
│ │ ├── Form | ||
│ │ │ └── ... | ||
│ │ └── ... | ||
│ └── Period | ||
│ └── ... | ||
└── Company | ||
└── ... | ||
``` | ||
|
||
The income information is first grouped by the paying company. | ||
Then by period (usually by month). | ||
Then in forms (blanket) with fields. | ||
|
||
Forms can be layered so a form may contain multiple subforms. | ||
|
||
Descriptions of the forms and fields can be found in 'underbilag 1' here: <https://info.skat.dk/data.aspx?oid=2248828&chk=220344> | ||
|
||
Example: The form 16001 field 100000000000000057 is "A-indkomst, hvoraf der betales AM-bidrag". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[project] | ||
name = "python-skat-webservice" | ||
version = "0.1.0" | ||
description = "This project makes it easier to use the SKAT webservices." | ||
readme = "README.md" | ||
requires-python = ">=3.11" | ||
|
||
dependencies = [ | ||
"lxml>=5.3.2", | ||
"requests>=2.32.3", | ||
"xmlsec>=1.3.15", | ||
] | ||
|
||
[build-system] | ||
requires = ["setuptools>=65.0"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[tool.uv] | ||
dev-dependencies = [ | ||
"flake8>=7.2.0", | ||
"pylint>=3.3.6", | ||
] |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"""This module contains common classes and functions.""" | ||
|
||
from dataclasses import dataclass | ||
|
||
|
||
@dataclass | ||
class CallerInfo: | ||
"""A dataclass that describes the caller of a webservice. | ||
caller_id: The id of the caller used to trace usage of the service. Can be anything. | ||
se_number: The SE-number of the caller. | ||
abonnent_type_kode: The AbonnentTypeKode from the service agreement. | ||
abonnement_type_kode: The AbonnementTypeKode from the service agreement. | ||
adgang_formaal_type_kode: The AdgangFormaalTypeKode from the service agreement. | ||
""" | ||
caller_id: str | ||
se_number: str | ||
abonnent_type_kode: str | ||
abonnement_type_kode: str | ||
adgang_formaal_type_kode: str |
121 changes: 121 additions & 0 deletions
121
src/python_skat_webservice/indkomst_oplysning_person_hent.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
"""This module interacts with the IndkomstOplysningPersonHent webservice.""" | ||
|
||
from lxml import etree | ||
import requests | ||
|
||
from python_skat_webservice.soap_signer import SOAPSigner | ||
from python_skat_webservice.common import CallerInfo | ||
|
||
NSMAP = { | ||
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", | ||
"ns0": "http://rep.oio.dk/skat.dk/eindkomst/", | ||
"ns1": "http://rep.oio.dk/skat.dk/basis/kontekst/xml/schemas/2006/09/01/", | ||
"ns2": "http://rep.oio.dk/skat.dk/eindkomst/class/abonnenttype/xml/schemas/20071202/", | ||
"ns3": "http://rep.oio.dk/skat.dk/eindkomst/class/abonnementtype/xml/schemas/20071202/", | ||
"ns4": "http://rep.oio.dk/skat.dk/eindkomst/class/adgangformaaltype/xml/schemas/20071202/", | ||
"ns5": "http://rep.oio.dk/skat.dk/motor/class/virksomhed/xml/schemas/20080401/", | ||
"ns6": "http://rep.oio.dk/skat.dk/eindkomst/class/indkomstoplysningadgangmedarbejderidentifikator/xml/schemas/20071202/", | ||
"ns7": "http://rep.oio.dk/cpr.dk/xml/schemas/core/2005/03/18/", | ||
"ns8": "http://rep.oio.dk/skat.dk/eindkomst/class/soegeaarmaanedfrakode/xml/schemas/20071202/", | ||
"ns9": "http://rep.oio.dk/skat.dk/eindkomst/class/soegeaarmaanedtilkode/xml/schemas/20071202/", | ||
"ns10": "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", | ||
} | ||
|
||
|
||
def create_envelope(*, cpr: str, month_from: str, month_to: str, transaction_id: str, caller_info: CallerInfo, soap_signer: SOAPSigner) -> str: | ||
"""Create a SOAP envelope for calling the service. | ||
|
||
Args: | ||
cpr: The cpr-number to search on. | ||
month_from: The beginning of the search interval. Formatted as "yyyymm" | ||
month_to: The end of the search interval. Formatted as "yyyymm" | ||
transaction_id: An id to identify the transaction. Can be anything. | ||
caller_info: A CallerInfo object that describes the caller. | ||
soap_signer: The SOAPSigner object used to sign the call. | ||
|
||
Returns: | ||
The signed SOAP envelope as a string. | ||
""" | ||
# Create envelope | ||
envelope = etree.Element(f"{{{NSMAP['soap-env']}}}Envelope", nsmap=NSMAP) | ||
|
||
# Create SOAP Body | ||
body = etree.SubElement(envelope, f"{{{NSMAP['soap-env']}}}Body") | ||
|
||
# Main request structure | ||
request = etree.SubElement(body, f"{{{NSMAP['ns0']}}}IndkomstOplysningPersonHent_I") | ||
|
||
# HovedOplysninger | ||
hoved = etree.SubElement(request, f"{{{NSMAP['ns1']}}}HovedOplysninger") | ||
tid = etree.SubElement(hoved, f"{{{NSMAP['ns1']}}}TransaktionIdentifikator") | ||
tid.text = transaction_id | ||
|
||
# IndkomstOplysningPersonInddata | ||
inddata = etree.SubElement(request, f"{{{NSMAP['ns0']}}}IndkomstOplysningPersonInddata") | ||
|
||
# AbonnentAdgangStruktur | ||
adgang = etree.SubElement(inddata, f"{{{NSMAP['ns0']}}}AbonnentAdgangStruktur") | ||
etree.SubElement(adgang, f"{{{NSMAP['ns2']}}}AbonnentTypeKode").text = caller_info.abonnent_type_kode | ||
etree.SubElement(adgang, f"{{{NSMAP['ns3']}}}AbonnementTypeKode").text = caller_info.abonnement_type_kode | ||
etree.SubElement(adgang, f"{{{NSMAP['ns4']}}}AdgangFormaalTypeKode").text = caller_info.adgang_formaal_type_kode | ||
|
||
# AbonnentStruktur | ||
abonnent = etree.SubElement(inddata, f"{{{NSMAP['ns0']}}}AbonnentStruktur") | ||
virk_struct = etree.SubElement(abonnent, f"{{{NSMAP['ns0']}}}AbonnentVirksomhedStruktur") | ||
virk = etree.SubElement(virk_struct, f"{{{NSMAP['ns0']}}}AbonnentVirksomhed") | ||
etree.SubElement(virk, f"{{{NSMAP['ns5']}}}VirksomhedSENummerIdentifikator").text = caller_info.se_number | ||
etree.SubElement(abonnent, f"{{{NSMAP['ns6']}}}IndkomstOplysningAdgangMedarbejderIdentifikator").text = caller_info.caller_id | ||
|
||
# IndkomstOplysningValg | ||
valg = etree.SubElement(inddata, f"{{{NSMAP['ns0']}}}IndkomstOplysningValg") | ||
samling = etree.SubElement(valg, f"{{{NSMAP['ns0']}}}IndkomstPersonSamling") | ||
soege = etree.SubElement(samling, f"{{{NSMAP['ns0']}}}PersonIndkomstSoegeStruktur") | ||
etree.SubElement(soege, f"{{{NSMAP['ns7']}}}PersonCivilRegistrationIdentifier").text = cpr | ||
lukket = etree.SubElement(soege, f"{{{NSMAP['ns0']}}}SoegeAarMaanedLukketStruktur") | ||
etree.SubElement(lukket, f"{{{NSMAP['ns8']}}}SoegeAarMaanedFraKode").text = month_from | ||
etree.SubElement(lukket, f"{{{NSMAP['ns9']}}}SoegeAarMaanedTilKode").text = month_to | ||
|
||
# Sign envelope and create xml string | ||
soap_signer.sign_soap_envelope(envelope) | ||
return etree.tostring(envelope, pretty_print=False, xml_declaration=True, encoding="utf-8").decode() | ||
|
||
|
||
def search_income(*, cpr: str, month_from: str, month_to: str, transaction_id: str, caller_info: CallerInfo, soap_signer: SOAPSigner, timeout: int = 30) -> str: | ||
"""Search the income information on the given cpr-number for the given month interval. | ||
|
||
Args: | ||
cpr: The cpr-number to search on. | ||
month_from: The beginning of the search interval. Formatted as "yyyymm" | ||
month_to: The end of the search interval. Formatted as "yyyymm" | ||
transaction_id: An id to identify the transaction. Can be anything. | ||
caller_info: A CallerInfo object that describes the caller. | ||
soap_signer: The SOAPSigner object used to sign the call. | ||
timeout: The time in seconds to wait for the http call. | ||
|
||
Raises: | ||
HTTPError: If the server didn't return a 200 status code. | ||
|
||
Returns: | ||
The raw xml response from the server. | ||
""" | ||
msg = create_envelope( | ||
cpr=cpr, | ||
month_from=month_from, | ||
month_to=month_to, | ||
transaction_id=transaction_id, | ||
caller_info=caller_info, | ||
soap_signer=soap_signer | ||
) | ||
|
||
# Call service | ||
url = "https://services.extranet.skat.dk/vericert/services/IndkomstOplysningPersonHentV2ServicePort" | ||
|
||
headers = { | ||
'content-type': 'text/xml', | ||
'SOAPAction': 'IndkomstOplysningPersonHent' | ||
} | ||
|
||
response = requests.post(url=url, data=msg, headers=headers, timeout=timeout) | ||
response.raise_for_status() | ||
|
||
return response.text |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.