diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d50a580..5693d32 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,7 +22,7 @@ "88" ], "python.linting.enabled": true, - "editor.formatOnPaste": false, + "editor.formatOnPaste": true, "editor.formatOnSave": true, "editor.formatOnType": true, "files.trimTrailingWhitespace": true, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47b22a7..a4d0ea6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black name: black diff --git a/LICENSE b/LICENSE index d243e4b..1733453 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Patrik +Copyright (c) 2022 Patrik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f2efcc3..26d16d2 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,3 @@ [![PyPI version](https://badge.fury.io/py/pygleif.svg)](https://badge.fury.io/py/pygleif) -This is a Python class that queries the API of GLEIF.org to return data about a specific entity (corporation.) It can also parse the XML-files provided by GLEIF. It is also possible to search for organisation id to find LEI codes and also to get child notes for a specific LEI code. - -Usage if you query the API: - -``` -from pygleif import GLEIF - -data = GLEIF('8RS0AKOLN987042F2V04') -print(data.registration.initial_registration_date) -``` - -## 1. How to use - -Usage if you use the XML files: - -``` -from pygleif import GLEIFParseRelationshipRecord - -# XML is the content (converted to text) of a -data = GLEIFParseRelationshipRecord(XML) -print(data.raw.Relationship.StartNode.NodeID.text) # Uses BeautifulSoup to convert to object - -``` - -There are also some examples available in the sources' example folder. +This library queries the API of GLEIF.org to return data about a specific entity company. The result is typed using `Pydantic` It is also possible to search for organisation id to find LEI codes and also to get child notes for a specific LEI code. diff --git a/example.py b/example.py deleted file mode 100644 index 0df72fe..0000000 --- a/example.py +++ /dev/null @@ -1,8 +0,0 @@ -from pygleif import Search, DirectChild -from pprint import pprint - - -gleif_search_data = Search('989932667') -gleif_child_data = DirectChild('549300ZQH0FIF1P0MX67') - -pprint(gleif_child_data.valid_child_records) diff --git a/examples/example_download_parse_xml.py b/examples/example_download_parse_xml.py deleted file mode 100644 index 4c0f00b..0000000 --- a/examples/example_download_parse_xml.py +++ /dev/null @@ -1,54 +0,0 @@ -# Hack to allow relative import above top level package -import os -import sys - -folder = os.path.dirname(os.path.abspath(__file__)) # noqa -sys.path.insert(0, os.path.normpath("%s/.." % folder)) # noqa - -import os -import xml.etree.ElementTree as ET -from zipfile import ZipFile - -import arrow -import wget - -from pygleif import GLEIFParseRelationshipRecord -from pygleif.const import URL_LEVEL2_CONCAT_FILES - -debug = 1 - -today = arrow.now().format("YYYYMMDD") -level_2_url = URL_LEVEL2_CONCAT_FILES.replace("%date%", today) -save_name = "file" + today + ".zip" -extracted_file = today + "-gleif-concatenated-file-rr.xml" - -if debug == 0: - - # Make sure there is no file already - os.remove(save_name) - - # Download file - wget.download(level_2_url, save_name) - - # opening the zip file in READ mode - with ZipFile(save_name, "r") as zip: - print("Extracting all the files now...") - zip.extractall() - os.remove(save_name) - -tree = ET.parse(extracted_file) -root = tree.getroot() - -# for i in range(len(root[1])): -for i in range(5): - xml_data = ET.tostring(root[1][i], encoding="utf8", method="xml") - data = GLEIFParseRelationshipRecord(xml_data) - - relationship_type = data.raw.Relationship.RelationshipType.text - parent = data.raw.Relationship.StartNode.NodeID.text - child = data.raw.Relationship.EndNode.NodeID.text - - print("Relationship type: " + relationship_type) - print("Parent: " + parent) - print("Child: " + child) - print("") diff --git a/examples/query_api.py b/examples/query_api.py index 53b9067..1aa2611 100644 --- a/examples/query_api.py +++ b/examples/query_api.py @@ -1,20 +1,17 @@ +"""Example.""" # Hack to allow relative import above top level package import os import sys -folder = os.path.dirname(os.path.abspath(__file__)) # noqa -sys.path.insert(0, os.path.normpath("%s/.." % folder)) # noqa -from pprint import pprint +folder = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.normpath("%s/.." % folder)) +from pygleif import PyGleif -from pygleif import Search -from pygleif.gleif import GLEIF - -gleif_data = GLEIF("506700GE1G29325QX363") -gleif_search = Search("986228608") - - -pprint((gleif_data.raw)) -print(gleif_data.entity.legal_jurisdiction) -print(gleif_data.entity.legal_form) -print(gleif_data.entity.business_register_entity_id) -print(gleif_search.lei) +for entity in [ + "549300MLUDYVRQOOXS22", + "M312WZV08Y7LYUC71685", + "IGJSJL3JD5P30I6NJZ34", + "3C7474T6CDKPR9K6YT90", +]: + gleif: PyGleif = PyGleif(entity) + print(gleif.response.data.attributes.registration.initial_registration_date) diff --git a/pygleif/__init__.py b/pygleif/__init__.py index 9481911..8442ad6 100644 --- a/pygleif/__init__.py +++ b/pygleif/__init__.py @@ -1,6 +1,6 @@ """Make Python interpret this as a package.""" -from .gleif import GLEIF -from .search import DirectChild, GLEIFParseRelationshipRecord, Search +from .gleif import PyGleif +from .search import Search -__all__ = ["GLEIF"] +__all__ = ["PyGleif", "Search"] diff --git a/pygleif/address.py b/pygleif/address.py deleted file mode 100644 index a2111bf..0000000 --- a/pygleif/address.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Address.""" -from pygleif.const import ( - ATTR_ADDRESS_CITY, - ATTR_ADDRESS_COUNTRY, - ATTR_ADDRESS_LINE1, - ATTR_ADDRESS_POSTAL_CODE, - ATTR_ADDRESS_REGION, -) - - -class Address: - def __init__(self, address, address_type): - self._address = address - self.address_type = address_type - - @property - def raw(self): - return self._address.raw.get(self.address_type) - - @property - def city(self): - return self.raw.get(ATTR_ADDRESS_CITY, None) - - @property - def country(self): - return self.raw.get(ATTR_ADDRESS_COUNTRY, None) - - @property - def line1(self): - address_lines = self.raw.get(ATTR_ADDRESS_LINE1, None) - if address_lines: - return address_lines[0] - - @property - def postal_code(self): - return self.raw.get(ATTR_ADDRESS_POSTAL_CODE, None) - - @property - def region(self): - return self.raw.get(ATTR_ADDRESS_REGION, None) diff --git a/pygleif/api/__init__.py b/pygleif/api/__init__.py new file mode 100644 index 0000000..a66b904 --- /dev/null +++ b/pygleif/api/__init__.py @@ -0,0 +1,12 @@ +"""Pydantic Representation of models.""" +from pydantic import BaseModel, Field + +from .data import Data +from .meta import Meta + + +class GLEIFResponse(BaseModel): + """Represent a base response.""" + + meta: Meta = Field(alias="meta") + data: Data = Field(alias="data") diff --git a/pygleif/api/data.py b/pygleif/api/data.py new file mode 100644 index 0000000..dcba68a --- /dev/null +++ b/pygleif/api/data.py @@ -0,0 +1,145 @@ +"""Data.""" +from datetime import datetime +from typing import Any, List, Optional + +from pydantic import BaseModel, Field + + +class ValidatedAt(BaseModel): + """Represent validated at information.""" + + id: str = Field(alias="id") + other: Optional[str] = Field(alias="other") + + +class Registration(BaseModel): + """Represent registration information.""" + + corroboration_level: str = Field(alias="corroborationLevel") + initial_registration_date: datetime = Field(alias="initialRegistrationDate") + last_update_date: datetime = Field(alias="lastUpdateDate") + managing_lou: str = Field(alias="managingLou") + next_renewal_date: datetime = Field(alias="nextRenewalDate") + other_validation_authorities: List[Any] = Field(alias="otherValidationAuthorities") + status: str = Field(alias="status") + validated_as: str = Field(alias="validatedAs") + validated_at: ValidatedAt = Field(alias="validatedAt") + + +class GeneralEntity(BaseModel): + """Represent a general entity .""" + + lei: Optional[str] = Field(alias="lei") + name: Optional[str] = Field(alias="name") + + +class Address(BaseModel): + """Represent address .""" + + language: str = Field(alias="language") + address_lines: List[str] = Field(alias="addressLines") + address_number: Optional[str] = Field(alias="addressNumber") + address_number_within_building: Optional[str] = Field( + alias="addressNumberWithinBuilding" + ) + mail_routing: Optional[str] = Field(alias="mailRouting") + city: str = Field(alias="city") + region: str = Field(alias="region") + country: str = Field(alias="country") + postal_code: str = Field(alias="postalCode") + + +class Expiration(BaseModel): + """Represent expiration data.""" + + date: Optional[str] = Field(alias="date") + reason: Optional[str] = Field(alias="reason") + + +class LegalForm(BaseModel): + """Represent the legal form.""" + + id: Optional[str] = Field(alias="id") + other: Optional[str] = Field(alias="other") + + +class Name(BaseModel): + """Represent the name.""" + + language: str = Field(alias="language") + name: str = Field(alias="name") + type: Optional[str] = Field(alias="type") + + +class RegisteredAt(BaseModel): + """Represent registered at.""" + + id: str = Field(alias="id") + other: Optional[str] = Field(alias="other") + + +class Entity(BaseModel): + """Represent entity information.""" + + associated_entity: GeneralEntity = Field(alias="associatedEntity") + category: str = Field(alias="category") + creation_date: Optional[str] = Field(alias="creationDate") + event_groups: List[Any] = Field(alias="eventGroups") + expiration: Expiration = Field(alias="expiration") + headquarters_address: Address = Field(alias="headquartersAddress") + jurisdiction: str = Field(alias="jurisdiction") + legal_address: Address = Field(alias="legalAddress") + legal_form: LegalForm = Field(alias="legalForm") + legal_name: Name = Field("legalName") + other_addresses: List[Any] = Field(alias="otherAddresses") + other_names: Name = Field("otherNames") + registered_as: str = Field(alias="registeredAs") + registered_at: RegisteredAt = Field("registeredAt") + status: str = Field(alias="status") + successor_entities: List[Any] = Field(alias="successorEntities") + sub_category: Optional[str] = Field(alias="subCategory") + successor_entity: GeneralEntity = Field(alias="successorEntity") + transliteraded_other_names: List[Any] = Field(alias="transliteratedOtherNames") + + +class Attributes(BaseModel): + """Represent attribute information.""" + + bic: Optional[List[str]] = Field(alias="bic") + lei: str = Field(alias="lei") + entity: Entity = Field(alias="entity") + registration: Registration = Field(alias="registration") + + +class LinkData(BaseModel): + """Represent a link .""" + + self: Optional[str] = Field(alias="self") + related: Optional[str] = Field(alias="related") + reporting_exception: Optional[str] = Field(alias="reporting-exception") + + +class Links(BaseModel): + """Represent a link .""" + + links: LinkData = Field(alias="links") + + +class Relationships(BaseModel): + """Represent a relationships .""" + + managing_lou: Links = Field(alias="managing-lou") + lei_issuer: Links = Field(alias="lei-issuer") + field_modifications: Links = Field(alias="field-modifications") + direct_parent: Links = Field(alias="direct-parent") + ultimate_parent: Links = Field(alias="ultimate-parent") + + +class Data(BaseModel): + """Represent data information.""" + + id: str = Field(alias="id") + type: str = Field(alias="type") + attributes: Attributes = Field(alias="attributes") + links: LinkData = Field(alias="links") + relationships: Relationships = Field(alias="relationships") diff --git a/pygleif/api/meta.py b/pygleif/api/meta.py new file mode 100644 index 0000000..d91cf22 --- /dev/null +++ b/pygleif/api/meta.py @@ -0,0 +1,16 @@ +"""Meta data.""" +from datetime import datetime + +from pydantic import BaseModel, Field + + +class GoldenCopy(BaseModel): + """Represent golden copy information.""" + + publish_date: datetime = Field(alias="publishDate") + + +class Meta(BaseModel): + """Represent meta information.""" + + golden_copy: GoldenCopy = Field(alias="goldenCopy") diff --git a/pygleif/const.py b/pygleif/const.py index 236a07e..29429be 100644 --- a/pygleif/const.py +++ b/pygleif/const.py @@ -1,43 +1,12 @@ +"""Constants.""" URL_API = "https://api.gleif.org/api/v1/lei-records/" -URL_LEVEL2_CONCAT_FILES = ( - "https://leidata.gleif.org/api/v1/concatenated-files/rr/%date%/zip" -) URL_SEARCH = "https://api.gleif.org/api/v1/lei-records?filter%5Bfulltext%5D=" URL_DIRECT_CHILD = ( - "https://api.gleif.org/api/v1/lei-records/{}/direct-" "child-relationships" + "https://api.gleif.org/api/v1/lei-records/{}/direct-child-relationships" ) -ATTR_LEI = "lei" -ATTR_ENTITY_REGISTERED_AS = "registeredAs" -ATTR_JURISDICTION = "jurisdiction" -ATTR_ENTITY_LEGAL_NAME = "legalName" -ATTR_NAME = "name" -ATTR_STATUS = "status" -ATTR_ENTITY_LEGAL_FORM = "legalForm" -ATTR_ID = "id" - - -# RegistrationAuthorityEntityID -ATTR_ENTITY_ENTITY_STATUS = "EntityStatus" -ATTR_ENTITY_LEGAL_FORM_CODE = "EntityLegalFormCode" - -ATTR_INITIAL_REGISTRATION_DATE = "initialRegistrationDate" -ATTR_LAST_UPDATE_DATE = "lastUpdateDate" -ATTR_MANAGING_LOU = "managingLou" -ATTR_NEXT_RENEWAL_DATE = "nextRenewalDate" -ATTR_VALIDATION_SOURCES = "corroborationLevel" - -ATTR_ADDRESS_TYPE_HQ = "headquartersAddress" -ATTR_ADDRESS_TYPE_LEGAL = "legalAddress" -ATTR_ADDRESS_CITY = "city" -ATTR_ADDRESS_COUNTRY = "country" -ATTR_ADDRESS_LINE1 = "addressLines" -ATTR_ADDRESS_POSTAL_CODE = "postalCode" -ATTR_ADDRESS_REGION = "region" -ATTR_REGISTER = "@register" ATTR_REGISTRATION = "registration" -ATTR_ENTITY = "entity" # Allowed attributes for a record ALLOW_ATTR_REGISTRATION_STATUS = ["ISSUED", "LAPSED"] diff --git a/pygleif/entity.py b/pygleif/entity.py deleted file mode 100644 index 183cf77..0000000 --- a/pygleif/entity.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Entity.""" - -from pygleif.address import Address -from pygleif.const import ( - ATTR_ADDRESS_TYPE_HQ, - ATTR_ADDRESS_TYPE_LEGAL, - ATTR_ENTITY, - ATTR_ENTITY_LEGAL_FORM, - ATTR_ENTITY_LEGAL_NAME, - ATTR_ENTITY_REGISTERED_AS, - ATTR_ID, - ATTR_JURISDICTION, - ATTR_NAME, - ATTR_STATUS, - LEGAL_FORMS, -) - - -class GLEIFEntity: - def __init__(self, entity): - self._entity = entity - - @property - def raw(self): - """Return raw data.""" - return self._entity.raw[ATTR_ENTITY] - - @property - def registration_authority_entity_id(self): - """ - Some entities return the register entity id, - but other do not. Unsure if this is a bug or - inconsistently registered data. - """ - return self.raw.get(ATTR_ENTITY_REGISTERED_AS) - - @property - def entity_status(self): - """Return entity status.""" - return self.raw.get(ATTR_STATUS) - - @property - def legal_form(self): - """In some cases, the legal form is stored in the JSON-data. - In other cases, an ELF-code, consisting of mix of exactly - four letters and numbers are stored. This ELF-code - can be looked up in a registry where a code maps to - a organizational type. ELF-codes are not unique, - it can reoccur under different names in different - countries""" - legal_form_id = self.raw.get(ATTR_ENTITY_LEGAL_FORM).get(ATTR_ID) - - if ( - self.legal_jurisdiction in LEGAL_FORMS - and legal_form_id in LEGAL_FORMS[self.legal_jurisdiction] - ): - return LEGAL_FORMS[self.legal_jurisdiction][legal_form_id] - - return f"ELF code: {legal_form_id}" - - @property - def legal_jurisdiction(self): - """Return legal jurisdiction.""" - return self.raw.get(ATTR_JURISDICTION) - - @property - def legal_name(self): - """Return legal name.""" - return self.raw.get(ATTR_ENTITY_LEGAL_NAME).get(ATTR_NAME) - - @property - def headquarters_address(self): - return Address(self, ATTR_ADDRESS_TYPE_HQ) - - @property - def legal_address(self): - return Address(self, ATTR_ADDRESS_TYPE_LEGAL) diff --git a/pygleif/gleif.py b/pygleif/gleif.py index 9bd5696..db414f5 100644 --- a/pygleif/gleif.py +++ b/pygleif/gleif.py @@ -1,44 +1,12 @@ -from .const import ( - ATTR_LEI, - URL_API, -) -import urllib.request as url -import json +"""GLEIF API.""" +from .api import GLEIFResponse +from .utils import load_json -from .entity import GLEIFEntity -from .registration import GLEIFRegistration +class PyGleif: + """Query GLEIF API and return response.""" -class GLEIF: - """Parse JSON from GLEIF registry. Supports v1 of API.""" - - def __init__(self, lei_code): - self.lei_code = lei_code - - @property - def json_data(self): - return url.urlopen(URL_API + self.lei_code) - - @property - def lei_exists(self): - try: - self.raw - return False - except IndexError: - return True - - @property - def raw(self): - return json.loads(self.json_data.read().decode("UTF-8"))["data"]["attributes"] - - @property - def lei(self): - return self.raw[ATTR_LEI] - - @property - def entity(self): - return GLEIFEntity(self) - - @property - def registration(self): - return GLEIFRegistration(self) + def __init__(self, lei_code: str) -> None: + """Init class.""" + json_data = load_json(lei_code) + self.response = GLEIFResponse(**json_data) diff --git a/pygleif/registration.py b/pygleif/registration.py deleted file mode 100644 index b2ebf53..0000000 --- a/pygleif/registration.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Registration data.""" -from dateutil import parser - -from pygleif.const import ( - ATTR_INITIAL_REGISTRATION_DATE, - ATTR_LAST_UPDATE_DATE, - ATTR_MANAGING_LOU, - ATTR_NEXT_RENEWAL_DATE, - ATTR_REGISTRATION, - ATTR_STATUS, - ATTR_VALIDATION_SOURCES, -) - - -class GLEIFRegistration: - def __init__(self, registration): - self._registration = registration - - @property - def raw(self): - return self._registration.raw.get(ATTR_REGISTRATION) - - @property - def initial_registration_date(self): - if ATTR_INITIAL_REGISTRATION_DATE in self.raw: - return parser.parse(self.raw.get(ATTR_INITIAL_REGISTRATION_DATE)) - - @property - def last_update_date(self): - if ATTR_LAST_UPDATE_DATE in self.raw: - return parser.parse(self.raw.get(ATTR_LAST_UPDATE_DATE)) - - @property - def managing_lou(self): - if ATTR_MANAGING_LOU in self.raw: - return self.raw[ATTR_MANAGING_LOU] - - @property - def next_renewal_date(self): - if ATTR_NEXT_RENEWAL_DATE in self.raw: - return parser.parse(self.raw[ATTR_NEXT_RENEWAL_DATE]) - - @property - def registration_status(self): - if ATTR_STATUS in self.raw: - return self.raw[ATTR_STATUS] - - @property - def validation_sources(self): - if ATTR_VALIDATION_SOURCES in self.raw: - return self.raw[ATTR_VALIDATION_SOURCES] diff --git a/pygleif/search.py b/pygleif/search.py index 4cf1587..f27e5a5 100644 --- a/pygleif/search.py +++ b/pygleif/search.py @@ -2,9 +2,7 @@ import json from urllib import request as url -from bs4 import BeautifulSoup - -from pygleif.const import ALLOW_ATTR_REGISTRATION_STATUS, URL_DIRECT_CHILD, URL_SEARCH +from pygleif.const import ALLOW_ATTR_REGISTRATION_STATUS, URL_SEARCH class Search: @@ -45,43 +43,3 @@ def lei(self): return self.valid_record["lei"] except (IndexError, TypeError): return None - - -class GLEIFParseRelationshipRecord: - def __init__(self, record_xml): - self.record_xml = record_xml - - @property - def raw(self): - return BeautifulSoup(self.record_xml, "xml") - - -class DirectChild: - def __init__(self, lei=""): - """Init class.""" - # Allow searching using organisation number - self.lei = lei - - @property - def json_data(self): - """Get raw data from the service.""" - return url.urlopen(URL_DIRECT_CHILD.format(self.lei)) - - @property - def raw(self): - """Return parsed json.""" - return json.loads(self.json_data.read().decode("UTF-8")) - - @property - def valid_child_records(self): - child_lei = list() - - """Loop through data to find a valid record. Return list of LEI.""" - for d in self.raw["data"]: - - # We're not very greedy here, but it seems some records have - # lapsed even through the issuer is active - if d["attributes"]["relationship"]["status"] in ["ACTIVE"]: - child_lei.append(d["attributes"]["relationship"]["startNode"]["id"]) - - return child_lei diff --git a/pygleif/utils.py b/pygleif/utils.py new file mode 100644 index 0000000..f023510 --- /dev/null +++ b/pygleif/utils.py @@ -0,0 +1,14 @@ +"""Utility functions.""" +from __future__ import annotations + +import json +from typing import Any, Dict, List, Union, cast +import urllib.request as url + +from .const import URL_API + + +def load_json(lei_code: str) -> list[Any] | dict[Any, Any]: + """Download data as JSON.""" + with url.urlopen(f"{URL_API}{lei_code}") as fdesc: + return cast(Union[Dict[Any, Any], List[Any]], json.loads(fdesc.read())) diff --git a/pyproject.toml b/pyproject.toml index b2a1e85..810237c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,45 @@ [tool.black] target-version = ["py38", "py39", "py310"] +[tool.isort] +# https://github.com/PyCQA/isort/wiki/isort-Settings +profile = "black" +# will group `import x` and `from x import` of the same module. +force_sort_within_sections = true +known_first_party = [ + "pygleif", + "tests", +] +forced_separate = [ + "tests", +] +combine_as_imports = true + +[tool.pylint.MASTER] +py-version = "3.8" + + +[tool.pylint.BASIC] +class-const-naming-style = "any" + +[tool.pylint."MESSAGES CONTROL"] + + +[tool.pylint.REPORTS] +score = false + +[tool.pylint.FORMAT] +expected-line-ending-format = "LF" + +[tool.pylint.EXCEPTIONS] +overgeneral-exceptions = [ + "BaseException", + "Exception", +] + +[tool.pylint.TYPING] +runtime-typing = false + [tool.pytest.ini_options] testpaths = [ "tests", diff --git a/requirements.txt b/requirements.txt index 4eaf30f..7c90e46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ arrow==1.2.2 wget==3.2.0 beautifulsoup4==4.11.1 lxml==4.9.0 +pydantic==1.9.1 python-dateutil==2.8.2 diff --git a/setup.cfg b/setup.cfg index 143e64d..eb9c9d9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,4 +5,22 @@ description-file = README.md testpaths = tests [flake8] -exclude = .venv,.git,.tox,docs,venv,bin,lib,build,setup.py \ No newline at end of file +exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build,setup.py +doctests = True +# To work with Black +max-line-length = 88 +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring +# W504 line break after binary operator +# E402 import at top (for examples) +ignore = + E501, + W503, + E203, + D202, + W504, + E402, +# Ignore to secure identical run tox/pre-commit and behaviour like before installing plugins +# these errors are to be corrected in the code diff --git a/setup.py b/setup.py index 296d89b..b55441a 100644 --- a/setup.py +++ b/setup.py @@ -32,12 +32,11 @@ download_url=DOWNLOAD_URL, classifiers=[ "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", - "Topic :: Home Automation", + "Topic :: Finance", ], ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d420712 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests.""" diff --git a/tests/sample_data.py b/tests/sample_data.py deleted file mode 100644 index d9f2197..0000000 --- a/tests/sample_data.py +++ /dev/null @@ -1,157 +0,0 @@ -LEI_DATA = """[ - { - "LEI": { - "$": "097900BEJX0000001852" - }, - "Entity": { - "LegalName": { - "@xml:lang": "sk", - "$": "ZSE Energia, a.s." - }, - "TransliteratedOtherEntityNames": { - "TransliteratedOtherEntityName": [ - { - "@type": "AUTO_ASCII_TRANSLITERATED_LEGAL_NAME", - "$": "ZSE ENERGIA, AS" - } - ] - }, - "LegalAddress": { - "@xml:lang": "sk", - "FirstAddressLine": { - "$": "Čulenova 6" - }, - "City": { - "$": "Bratislava" - }, - "Country": { - "$": "SK" - }, - "PostalCode": { - "$": "81647" - } - }, - "HeadquartersAddress": { - "@xml:lang": "sk", - "FirstAddressLine": { - "$": "Čulenova 6" - }, - "City": { - "$": "Bratislava" - }, - "Country": { - "$": "SK" - }, - "PostalCode": { - "$": "81647" - } - }, - "RegistrationAuthority": { - "RegistrationAuthorityID": { - "$": "RA000526" - }, - "OtherRegistrationAuthorityID": { - "$": "Okresný súd Bratislava I - 3978/B" - }, - "RegistrationAuthorityEntityID": { - "$": "36677281" - } - }, - "LegalJurisdiction": { - "$": "SK" - }, - "LegalForm": { - "EntityLegalFormCode": { - "$": "2EEG" - } - }, - "EntityStatus": { - "$": "ACTIVE" - }, - "NextVersion": null - }, - "Registration": { - "InitialRegistrationDate": { - "$": "2014-09-23T00:00:00.00Z" - }, - "LastUpdateDate": { - "$": "2018-11-08T00:00:00.00Z" - }, - "RegistrationStatus": { - "$": "ISSUED" - }, - "NextRenewalDate": { - "$": "2019-12-13T00:00:00.00Z" - }, - "ManagingLOU": { - "$": "097900BEFH0000000217" - }, - "ValidationSources": { - "$": "FULLY_CORROBORATED" - }, - "ValidationAuthority": { - "ValidationAuthorityID": { - "$": "RA000526" - }, - "OtherValidationAuthorityID": { - "$": "Okresný súd Bratislava I - 3978/B" - }, - "ValidationAuthorityEntityID": { - "$": "36677281" - } - }, - "NextVersion": null - }, - "NextVersion": null - } -]""" - - -XML_DATA = """ - - 097900BEJX0000001852 - LEI - - - 315700FA4G1UK2XEQF20 - LEI - - IS_DIRECTLY_CONSOLIDATED_BY - - - 2017-12-14T00:00:00Z - ACCOUNTING_PERIOD - - - 2006-09-22T00:00:00Z - RELATIONSHIP_PERIOD - - - 2017-12-14T00:00:00Z - DOCUMENT_FILING_PERIOD - - - ACTIVE - - - ACCOUNTING_STANDARD - IFRS - - - - - ACCOUNTING_CONSOLIDATION - 1.00 - PERCENTAGE - - - - - 2017-12-14T00:00:00Z - 2017-12-14T00:00:00Z - PUBLISHED - 2018-12-13T00:00:00Z - 097900BEFH0000000217 - FULLY_CORROBORATED - OTHER_OFFICIAL_DOCUMENTS - """ diff --git a/tests/test_gleif.py b/tests/test_gleif.py index b961490..541025e 100644 --- a/tests/test_gleif.py +++ b/tests/test_gleif.py @@ -1,133 +1,20 @@ """Tests.""" -import datetime -import unittest +import pytest -from dateutil.tz import tzutc +from pygleif import PyGleif -from pygleif import GLEIF, GLEIFParseRelationshipRecord -from sample_data import XML_DATA +@pytest.fixture(scope="module") +def gleif_fixture_1() -> PyGleif: + """Fixture.""" + return PyGleif("549300MLUDYVRQOOXS22") -class TestGLEIFEntity(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.data = GLEIF("549300MLUDYVRQOOXS22") - def test_lei(self): - self.assertEqual(self.data.lei, "549300MLUDYVRQOOXS22") +def test_lei(gleif_fixture_1: PyGleif): + """Test LEI attribute.""" + assert gleif_fixture_1.response.data.attributes.lei, "549300MLUDYVRQOOXS22" - def test_gleif_entity_registration_authority_entity_id(self): - self.assertEqual(self.data.entity.registration_authority_entity_id, "917685991") - def test_gleif_entity_legal_jurisdiction(self): - self.assertEqual(self.data.entity.legal_jurisdiction, "NO") - - def test_gleif_entity_legal_form(self): - self.assertEqual(self.data.entity.legal_form, "Aksjeselskap") - - def test_gleif_entity_legal_name(self): - self.assertEqual(self.data.entity.legal_name, "NORDIC CREDIT RATING AS") - - def test_gleif_entity_entity_status(self): - self.assertEqual(self.data.entity.entity_status, "ACTIVE") - - -class TestGLEIFEntityLegalAddress(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.data = GLEIF("549300MLUDYVRQOOXS22") - - def test_gleif_entity_legal_address_line1(self): - self.assertEqual( - self.data.entity.legal_address.line1, "Biskop Gunnerus' gate 14A" - ) - - def test_gleif_entity_legal_address_city(self): - self.assertEqual(self.data.entity.legal_address.city, "OSLO") - - def test_gleif_entity_legal_address_region(self): - self.assertEqual(self.data.entity.legal_address.region, "NO-03") - - def test_gleif_entity_legal_address_country(self): - self.assertEqual(self.data.entity.legal_address.country, "NO") - - def test_gleif_entity_legal_address_postal_code(self): - self.assertEqual(self.data.entity.legal_address.postal_code, "0185") - - -class TestGLEIFEntityHQAddress(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.data = GLEIF("549300MLUDYVRQOOXS22") - - def test_gleif_entity_hq_address_line1(self): - self.assertEqual( - self.data.entity.headquarters_address.line1, "Biskop Gunnerus' gate 14A" - ) - - def test_gleif_entity_hq_address_city(self): - self.assertEqual(self.data.entity.headquarters_address.city, "OSLO") - - def test_gleif_entity_hq_address_region(self): - self.assertEqual(self.data.entity.headquarters_address.region, "NO-03") - - def test_gleif_entity_hq_address_country(self): - self.assertEqual(self.data.entity.headquarters_address.country, "NO") - - def test_gleif_entity_hq_address_postal_code(self): - self.assertEqual(self.data.entity.headquarters_address.postal_code, "0185") - - -class TestGLEIFEntityRegistration(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.data = GLEIF("549300MLUDYVRQOOXS22") - - def test_gleif_registration_initial_registration_date(self): - self.assertEqual( - self.data.registration.initial_registration_date, - datetime.datetime(2017, 4, 29, 2, 2, tzinfo=tzutc()), - ) - - def test_gleif_registration_last_update_date(self): - self.assertEqual( - datetime.datetime(2020, 11, 4, 8, 22, 43, tzinfo=tzutc()), - self.data.registration.last_update_date, - ) - - def test_gleif_registration_registration_status(self): - self.assertEqual(self.data.registration.registration_status, "ISSUED") - - def test_gleif_registration_next_renewal_date(self): - self.assertEqual( - self.data.registration.next_renewal_date, - datetime.datetime(2021, 12, 4, 12, 36, 42, tzinfo=tzutc()), - ) - - def test_gleif_registration_managing_lou(self): - self.assertEqual(self.data.registration.managing_lou, "529900F6BNUR3RJ2WH29") - - def test_gleif_registration_validation_sources(self): - self.assertEqual( - self.data.registration.validation_sources, "FULLY_CORROBORATED" - ) - - -class TestGLEIFRelationshipRecord(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.data = GLEIFParseRelationshipRecord(XML_DATA) - - def test_relationship_record(self): - self.assertEqual( - self.data.raw.Relationship.StartNode.NodeID.text, "097900BEJX0000001852" - ) - - -class TestEntitySpecialCase(unittest.TestCase): - def test_break_gleif_entity(self): - data = GLEIF("549300MWQEN1427O5L53") - self.assertEqual(data.entity.legal_form, "Aktiebolag") - - data = GLEIF("MAES062Z21O4RZ2U7M96") - self.assertEqual(data.entity.legal_form, "ELF code: ZRPO") +def test_id(gleif_fixture_1: PyGleif): + """Test ID attribute.""" + assert gleif_fixture_1.response.data.id, "549300MLUDYVRQOOXS22"