From 7c1cb713b437de8080f33fd762fac51015b9525a Mon Sep 17 00:00:00 2001 From: Rafael Ribeiro Date: Wed, 4 Nov 2020 00:22:20 -0300 Subject: [PATCH 1/3] Adiciona a funcionalidade de strict para os dados retornados da API e faz o bump da versao (#36) --- setup.py | 2 +- sgs/api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bfc6b6c..e7cf6a2 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup -__version__ = '2.0.5' +__version__ = '2.1.0' with open('README.rst', 'r', encoding='utf-8') as f: diff --git a/sgs/api.py b/sgs/api.py index 6601705..f90ebcb 100644 --- a/sgs/api.py +++ b/sgs/api.py @@ -48,7 +48,7 @@ def get_data_with_strict_range(ts_code: int, begin: str, end: str) -> List: period_start_date = to_datetime(begin, 'pt') try: - is_out_of_range = first_record_date < period_start_date + is_out_of_range = first_record_date < period_start_date #type: ignore if is_out_of_range: raise ValueError except TypeError: From 40c4363f51c3ea0d2d54443a5f8e502c83bba1f9 Mon Sep 17 00:00:00 2001 From: Rafael Ribeiro Date: Wed, 4 Nov 2020 01:27:40 -0300 Subject: [PATCH 2/3] Workflow (#39) * add tests.yml * set locale * fix publish to pypi --- .github/workflows/publish-to-pypi.yml | 80 ++++++++++++++------------- .github/workflows/tests.yml | 26 +++++++++ 2 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index f567953..b2afce6 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -1,42 +1,48 @@ -name: Publish Python 🐍 distributions 📦 to PyPI +name: Upload Python Package on: push: - branches: - - main - pull_request: - branches: - - main + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@master - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Install package dependencies - run: >- - python -m - pip install - .[dev] - - name: Test with pytest - run: python -m pytest - - name: Build a binary wheel and a source tarball - run: >- - python - setup.py - sdist - bdist_wheel - - name: Publish distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_password }} - - - + release: + name: Create Release + runs-on: ubuntu-18.04 + steps: + - name: Checkout code + uses: actions/checkout@master + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + Changes in this Release + draft: false + prerelease: false + deploy: + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel + - name: Build + run: | + python setup.py sdist bdist_wheel + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_password }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..dbb6fad --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,26 @@ +name: Test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-18.04 + strategy: + max-parallel: 4 + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + steps: + - uses: actions/checkout@v2 + + - name: Set locale + run: sudo apt-get update && sudo apt-get install tzdata locales -y && sudo locale-gen pt_BR.UTF-8 + + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies with pip + run: | + python -m pip install .[dev] + - run: pytest \ No newline at end of file From f58efb6303e14a90e12dc7a4b7929c28ff094efb Mon Sep 17 00:00:00 2001 From: O Pardal Date: Fri, 26 Feb 2021 22:30:05 -0300 Subject: [PATCH 3/3] =?UTF-8?q?Corre=C3=A7=C3=A3o=20strict=20e=20pequenas?= =?UTF-8?q?=20melhorias=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bump version on setup.py * add badges to readme * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Updates README.rst * Fix bug when time series search returned more than one result pages * Fix bug when time series search returned more than one result pages * Fix bug when time series search returned more than one result pages * Fix bug when time series search returned more than one result pages * Fix bug when time series search returned more than one result pages * Fix bug when time series search returned more than one result pages * Fix date parsing when only year is present * fix date parsing when only year is present * fix date parsing when only year is present * fix date parsing when only year is present * Fix date parsing when only year is present and enforce srtring inputs on date time transformation * fix date parsing when only year is present * fix date parsing when only year is present * fix date parsing when only year is present * fix date parsing when only year is present * fixes #28 * updating... * Deixando os branches develop iguais * Incluidas novas funcoes de data em common.py * Alterado modo strict da funcao time_serie * Testes para funcao search corrigidos * Disponibiliza a funcao apply_strict_range no init * Removida funcao get_data_with_strict_range do api.py * Teste api.py corrigido * Criacao da funcao get_series_code e atualizacao da funcao metadata * correcao de formatacao * Corrigindo formato das datas no dicionario de metadata * Separada a funcionalidade strict em novos arquivos * Update pytest.ini * strict incuded in pytest * metadata function now accepts lists and tuples * optional end implemented * simple refactoring * tests updates * pytest.ini fixed Co-authored-by: rafael --- pytest.ini | 1 + sgs/__init__.py | 1 + sgs/api.py | 43 +++-------------------------------- sgs/common.py | 50 ++++++++++++++++++++++++++++------------- sgs/dataframe.py | 9 ++++---- sgs/metadata.py | 24 +++++++++----------- sgs/search.py | 6 ++--- sgs/strict.py | 36 +++++++++++++++++++++++++++++ sgs/ts.py | 23 ++++++++++--------- tests/test_api.py | 9 -------- tests/test_common.py | 34 ++++++++++++++++++++++++++-- tests/test_dataframe.py | 14 +++++++++++- tests/test_metadata.py | 21 ++++++++++++++++- tests/test_search.py | 16 ++++++------- tests/test_strict.py | 22 ++++++++++++++++++ tests/test_ts.py | 14 +++++++++++- 16 files changed, 213 insertions(+), 110 deletions(-) create mode 100644 sgs/strict.py create mode 100644 tests/test_strict.py diff --git a/pytest.ini b/pytest.ini index 8c088b2..f8715fe 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,3 +10,4 @@ markers = mypy search ts + strict diff --git a/sgs/__init__.py b/sgs/__init__.py index 4cec0aa..08628d7 100644 --- a/sgs/__init__.py +++ b/sgs/__init__.py @@ -12,3 +12,4 @@ from .ts import time_serie from .metadata import metadata from .search import search_ts +from .strict import constrain diff --git a/sgs/api.py b/sgs/api.py index f90ebcb..babeef1 100644 --- a/sgs/api.py +++ b/sgs/api.py @@ -1,11 +1,10 @@ import functools -from typing import Union, List, Dict +from typing import List -import pandas as pd import requests from retrying import retry -from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER, to_datetime +from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER @retry(stop_max_attempt_number=MAX_ATTEMPT_NUMBER) @@ -14,6 +13,7 @@ def get_data(ts_code: int, begin: str, end: str) -> List: """ Requests time series data from the SGS API in json format. """ + end = begin if end is None else end url = ( "http://api.bcb.gov.br/dados/serie/bcdata.sgs.{}" @@ -22,40 +22,3 @@ def get_data(ts_code: int, begin: str, end: str) -> List: request_url = url.format(ts_code, begin, end) response = requests.get(request_url) return response.json() - -def get_data_with_strict_range(ts_code: int, begin: str, end: str) -> List: - - """ - Request time series data from the SGS API considering a strict range of dates. - - SGS API default behaviour returns the last stored value when selected date range have no data. - - It is possible to catch this behaviour when the first record date precedes the start date. - - This function enforces an empty data set when the first record date precedes the start date, avoiding records out of selected range. - - :param ts_code: time serie code. - :param begin: start date (DD/MM/YYYY). - :param end: end date (DD/MM/YYYY). - - :return: Data in json format or an empty list - :rtype: list - - """ - data = get_data(ts_code, begin, end) - - first_record_date = to_datetime(data[0]["data"], "pt") - period_start_date = to_datetime(begin, 'pt') - - try: - is_out_of_range = first_record_date < period_start_date #type: ignore - if is_out_of_range: - raise ValueError - except TypeError: - print("ERROR: Serie " + str(ts_code) + " - Please, use 'DD/MM/YYYY' format for date strings.") - data = [] - except ValueError: - print("WARNING: Serie " + str(ts_code) + " - There is no data for the requested period, but there's previous data.") - data = [] - - return data diff --git a/sgs/common.py b/sgs/common.py index 728596f..459bfe0 100644 --- a/sgs/common.py +++ b/sgs/common.py @@ -2,9 +2,9 @@ Shared functions. """ from datetime import datetime +from typing import Union, List, Tuple import locale -import re -from typing import Union +import pandas as pd import os @@ -12,35 +12,55 @@ MAX_ATTEMPT_NUMBER = 5 -def to_datetime(date_string: str, language: str) -> Union[datetime, str]: - """ Converts a date string to a datetime object """ - locales = {"pt": "pt_BR.utf-8", "en": "en_US.utf-8"} +def to_datetime(date_string: str, language: str) -> datetime: """ correct problem with locale in Windows platform """ if os.name == 'nt': locales = {"pt": "Portuguese_Brazil.1252", "en": "Portuguese_Brazil.1252"} - + else: + locales = {"pt": "pt_BR.utf-8", "en": "en_US.utf-8"} + locale.setlocale(locale.LC_TIME, locales[language]) dd_mm_aaaa = "%d/%m/%Y" mmm_aaaa = "%b/%Y" + aaaa = "%Y" - formats = [dd_mm_aaaa, mmm_aaaa] + formats = [dd_mm_aaaa, mmm_aaaa, aaaa] for fmt in formats: try: date = datetime.strptime(date_string, fmt) + if fmt == aaaa: + date = date.replace(day=31, month=12) break except ValueError: continue else: - yyyy = "[0-9]{4}" - if re.match(yyyy, date_string): - year = int(date_string) - month = 12 - day = 31 - date = datetime(year, month, day) - else: - return date_string # returns original value if cant parse + raise ValueError + return date + + +def to_datetime_string(date_string: str, language: str, strformat: str = "%Y-%m-%d") -> str: + try: + date = to_datetime(date_string, language).strftime(strformat) + except ValueError: + date = date_string return date + + +def get_series_codes(code_input: Union[int, List, Tuple, pd.DataFrame, pd.Series]) -> List: + + if isinstance(code_input, int): + codes = [code_input] + elif isinstance(code_input, pd.Series): + codes = [code_input.name] + elif isinstance(code_input, pd.DataFrame): + codes = list(code_input.columns) + elif isinstance(code_input, tuple): + codes = list(code_input) + else: + codes = code_input + + return codes diff --git a/sgs/dataframe.py b/sgs/dataframe.py index 9de03bf..4c68b82 100644 --- a/sgs/dataframe.py +++ b/sgs/dataframe.py @@ -1,16 +1,17 @@ """ Dataframe """ -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Tuple, Union, Optional import pandas as pd from . import api from . import search from .ts import time_serie +from .common import get_series_codes -def dataframe(ts_codes: Union[int, List, Tuple], start: str, end: str, strict: bool = False) -> pd.DataFrame: +def dataframe(ts_codes: Union[int, List, Tuple], start: str, end: Optional[str] = None, strict: bool = False) -> pd.DataFrame: """ Creates a dataframe from a list of time serie codes. @@ -36,11 +37,9 @@ def dataframe(ts_codes: Union[int, List, Tuple], start: str, end: str, strict: b 2018-01-05 0.026444 NaN """ - if isinstance(ts_codes, int): - ts_codes = [ts_codes] series = [] - for code in ts_codes: + for code in get_series_codes(ts_codes): ts = time_serie(code, start, end, strict) series.append(ts) diff --git a/sgs/metadata.py b/sgs/metadata.py index 717767a..fdd228e 100644 --- a/sgs/metadata.py +++ b/sgs/metadata.py @@ -1,11 +1,10 @@ -from typing import Optional, Dict, List, Union - import pandas as pd - +from typing import List, Tuple, Union from .search import search_ts +from .common import get_series_codes -def metadata(ts_code: Union[int, pd.DataFrame], language: str = "en") -> Optional[List]: +def metadata(ts_code: Union[int, List, Tuple, pd.DataFrame, pd.Series], language: str = "en") -> List: """Request metadata about a time serie or all time series in a pandas dataframe. :param ts_code: time serie code or pandas dataframe with time series as columns. @@ -27,14 +26,11 @@ def metadata(ts_code: Union[int, pd.DataFrame], language: str = "en") -> Optiona 'last_value': Timestamp('2019-05-01 00:00:00'), 'source': 'FGV'}] """ info = [] - if isinstance(ts_code, pd.core.frame.DataFrame): - for col in ts_code.columns: - col_info = search_ts(col, language) - if col_info is not None: - info.append(col_info[0]) - else: - info.append(None) - else: - col_info = search_ts(ts_code, language) - info.append(col_info) + for ts in get_series_codes(ts_code): + try: + metadata = search_ts(ts, language) + info.extend(metadata) + except NameError: + info.append(None) + return info diff --git a/sgs/search.py b/sgs/search.py index 716b4e0..61e6c81 100644 --- a/sgs/search.py +++ b/sgs/search.py @@ -6,7 +6,7 @@ from retrying import retry import pandas as pd -from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER, to_datetime +from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER, to_datetime_string @unique @@ -78,8 +78,8 @@ def parse_search_response(response, language: str) -> Optional[list]: try: df = pd.read_html(HTML, attrs={"id": "tabelaSeries"}, flavor="html5lib", skiprows=1)[0] - df[START] = df[START].map(lambda x: to_datetime(str(x), language)) - df[LAST] = df[LAST].map(lambda x: to_datetime(str(x), language)) + df[START] = df[START].map(lambda x: to_datetime_string(str(x), language, "%Y-%m-%d %H:%M:%S")) + df[LAST] = df[LAST].map(lambda x: to_datetime_string(str(x), language, "%Y-%m-%d %H:%M:%S")) col_names = { cols["code"]: "code", cols["name"]: "name", diff --git a/sgs/strict.py b/sgs/strict.py new file mode 100644 index 0000000..b85db23 --- /dev/null +++ b/sgs/strict.py @@ -0,0 +1,36 @@ +import pandas as pd +from typing import Union, Optional +from .common import to_datetime, get_series_codes + + +def constrain(data: Union[pd.DataFrame, pd.Series], start: str, end: Optional[str] = None) -> Union[pd.DataFrame, pd.Series]: + + """ + SGS API default behaviour returns the last stored value when selected date range have no data. + + This function enforces the date range selected by user. + + :param data: time_serie or dataframe to be filtered. + :param start: start date (DD/MM/YYYY). + :param end: end date (DD/MM/YYYY). + + :return: time_serie or dataframe + :rtype: pd.Series or pd.DataFrame + """ + + end = start if end is None else end + + try: + enforce_start = to_datetime(start, "pt") + enforce_end = to_datetime(end, "pt") + strict_data = data[data.index.to_series().between(enforce_start, enforce_end)] + if strict_data.empty or data.empty: + raise RuntimeError + except ValueError: + strict_data = data.drop(data.index) + print("ERROR: Please, use 'DD/MM/YYYY' format for date strings.") + except RuntimeError: + series = ','.join(str(code) for code in get_series_codes(data)) + print("WARNING: Serie(s) %s - There is no data for the requested period." % series) + + return strict_data diff --git a/sgs/ts.py b/sgs/ts.py index 7fb688f..c95fadc 100644 --- a/sgs/ts.py +++ b/sgs/ts.py @@ -1,17 +1,17 @@ """ Time Serie manipulation """ -from typing import Dict, List, Optional import numpy as np import pandas as pd +from typing import Optional from . import api -from . import search +from .strict import constrain from .common import to_datetime -def time_serie(ts_code: int, start: str, end: str, strict: bool = False) -> pd.Series: +def time_serie(ts_code: int, start: str, end: Optional[str] = None, strict: bool = False) -> pd.Series: """ Request a time serie data. @@ -34,18 +34,19 @@ def time_serie(ts_code: int, start: str, end: str, strict: bool = False) -> pd.S 2018-01-05 0.026444 2018-01-08 0.026444 """ - - if strict: - ts_data = api.get_data_with_strict_range(ts_code, start, end) - else: - ts_data = api.get_data(ts_code, start, end) - + values = [] index = [] - for i in ts_data: + for i in api.get_data(ts_code, start, end): values.append(i["valor"]) index.append(to_datetime(i["data"], "pt")) # Transform empty strings in null values values = [np.nan if value == "" else value for value in values] - return pd.Series(values, index, name=ts_code, dtype=np.float) + + ts = pd.Series(values, index, name=ts_code, dtype=np.float) + + if strict: + ts = constrain(ts, start, end) + + return ts diff --git a/tests/test_api.py b/tests/test_api.py index 9bae61f..35bf1e7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,7 +1,6 @@ import pytest from sgs import api -import pandas as pd @pytest.mark.api @@ -10,11 +9,3 @@ def test_get_data(): data = api.get_data(4, "02/01/2018", "31/01/2018") assert isinstance(data, list) assert len(data) == NUMBER_OF_LINES - -@pytest.mark.api -def test_get_data_with_strict_range(): - NUMBER_OF_LINES = 0 - data = api.get_data_with_strict_range(20577, "17/08/2019", "18/08/2019") - assert isinstance(data, list) - assert len(data) == NUMBER_OF_LINES - diff --git a/tests/test_common.py b/tests/test_common.py index 2d8812f..0d11bfa 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,8 +1,9 @@ from datetime import datetime import pytest -from sgs.common import to_datetime - +from sgs.common import to_datetime, to_datetime_string, get_series_codes +from sgs.dataframe import dataframe +from sgs.ts import time_serie @pytest.mark.common @pytest.mark.parametrize("language", ["pt", "en"]) @@ -26,3 +27,32 @@ def test_to_datetime_aaaaa(): expected = datetime(day=31, month=12, year=2018) date_string = '2018' assert to_datetime(date_string, 'pt') == expected + +@pytest.mark.common +@pytest.mark.parametrize( + "input_str, expected", [("01/05/2020", "2020-05-01"), + ("mai/2020", "2020-05-01"), ("2020", "2020-12-31")] +) +def test_to_datetime_string_pt(input_str, expected): + assert to_datetime_string(input_str, 'pt') == expected + +@pytest.mark.common +def test_to_datetime_string_en(): + expected = "2020-05-01" + assert to_datetime_string('may/2020', 'en') == expected + +@pytest.mark.common +def test_to_datetime_string_full_format(): + expected = "2020-12-31 00:00:00" + assert to_datetime_string('31/12/2020', 'pt', "%Y-%m-%d %H:%M:%S") == expected + +@pytest.mark.common +@pytest.mark.parametrize( + "input_code", [12, [12], (12), dataframe(12, '01/01/2020', '01/02/2020'), + time_serie(12, '01/01/2020', '01/02/2020')] +) +def test_get_series_codes(input_code): + codes = get_series_codes(input_code) + expected = [12] + assert codes == expected + assert isinstance(codes, list) \ No newline at end of file diff --git a/tests/test_dataframe.py b/tests/test_dataframe.py index 90ba137..b3215ff 100644 --- a/tests/test_dataframe.py +++ b/tests/test_dataframe.py @@ -13,7 +13,19 @@ def test_dataframe_multiple_ts(): ts_codes = [12, 433] df = dataframe(ts_codes, start="02/01/2018", end="31/01/2018") assert df.shape == (23, 2) - + +@pytest.mark.dataframe +def test_dataframe_single_date(): + ts_codes = [12, 433] + df = dataframe(ts_codes, start="02/01/2018") + assert df.shape == (2, 2) + +@pytest.mark.dataframe +def test_dataframe_single_date_with_strict_as_true(): + ts_codes = [12, 433] + df = dataframe(ts_codes, start="02/01/2018", strict=True) + assert df.shape == (1, 2) + @pytest.mark.dataframe def test_dataframe_one_with_strict_as_false(): df = dataframe(20577, start='17/08/2019', end='18/08/2019') diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4a4336a..906fa62 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,13 +1,24 @@ import pytest from sgs.metadata import * -from sgs import dataframe +from sgs import dataframe, time_serie @pytest.mark.metadata def test_metadata_returns_list_with_int_as_parameter(): assert isinstance(metadata(4), list) +@pytest.mark.metadata +def test_metadata_returns_list_with_list_as_parameter(): + meta = metadata([4,12]) + assert isinstance(meta, list) + assert len(meta) == 2 + +@pytest.mark.metadata +def test_metadata_returns_list_with_tuple_as_parameter(): + meta = metadata((4,12)) + assert isinstance(meta, list) + assert len(meta) == 2 @pytest.mark.metadata def test_metadata_returns_list_with_df_as_parameter(): @@ -16,3 +27,11 @@ def test_metadata_returns_list_with_df_as_parameter(): meta = metadata(df) assert isinstance(meta, list) assert len(meta) == len(ts_codes) + +@pytest.mark.metadata +def test_metadata_returns_list_with_ts_as_parameter(): + ts_code = 12 + ts = time_serie(ts_code, start="02/01/2018", end="31/01/2018") + meta = metadata(ts) + assert isinstance(meta, list) + assert len(meta) == 1 \ No newline at end of file diff --git a/tests/test_search.py b/tests/test_search.py index 8eaba82..8ef53c2 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -2,7 +2,8 @@ import requests from sgs.search import * -from sgs.common import to_datetime +from sgs.common import to_datetime, to_datetime_string + @pytest.mark.search @pytest.mark.parametrize("language", ["en", "pt"]) @@ -18,7 +19,7 @@ def test_search_by_code_english(): print(results) metadata = results[0] assert metadata['name'] == "BM&F Gold - gramme" - assert metadata['first_value'] == to_datetime("29/12/1989", "en") + assert metadata['first_value'] == to_datetime_string("29/12/1989", "en", "%Y-%m-%d %H:%M:%S") assert metadata['frequency'] == "D" @@ -28,7 +29,7 @@ def test_search_by_code_portuguese(): results = search_ts(code, Language.pt.value) metadata = results[0] assert metadata['name'] == "Ouro BM&F - grama" - assert metadata['first_value'] == to_datetime("29/12/1989", "pt") + assert metadata['first_value'] == to_datetime_string("29/12/1989", "pt", "%Y-%m-%d %H:%M:%S") assert metadata['frequency'] == "D" @@ -41,7 +42,7 @@ def test_search_by_code_portuguese(): "pt", { "name": "Ouro BM&F - grama", - "first_value": to_datetime("29/12/1989", "pt"), + "first_value": to_datetime_string("29/12/1989", "pt", "%Y-%m-%d %H:%M:%S"), "freq": "D", }, ), @@ -50,7 +51,7 @@ def test_search_by_code_portuguese(): "en", { "name": "BM&F Gold - gramme", - "first_value": to_datetime("29/12/1989", "en"), + "first_value": to_datetime_string("29/12/1989", "en", "%Y-%m-%d %H:%M:%S"), "freq": "D", }, ), @@ -62,7 +63,7 @@ def test_search_by_code_portuguese(): "Saldo de títulos de dívida emitidos por " "empresas e famílias - títulos privados" ), - "first_value": to_datetime("01/01/2013", "pt"), + "first_value": to_datetime_string("01/01/2013", "pt", "%Y-%m-%d %H:%M:%S"), "freq": "M", }, ), @@ -74,8 +75,7 @@ def test_search_by_code(query, language, expected): results = results[0] assert results["frequency"] == expected["freq"] assert results["name"] == expected["name"] - first_value = expected["first_value"] - assert results["first_value"] == first_value + assert results["first_value"] == expected["first_value"] @pytest.mark.search diff --git a/tests/test_strict.py b/tests/test_strict.py new file mode 100644 index 0000000..5f439fb --- /dev/null +++ b/tests/test_strict.py @@ -0,0 +1,22 @@ + +import pytest +import pandas as pd +from sgs.strict import constrain +from sgs.dataframe import dataframe +from sgs.ts import time_serie + +@pytest.mark.common +def test_constrain_on_df(): + df = dataframe(12, '01/01/2020', '01/05/2020') + strict_df = constrain(df, '01/01/2020', '01/04/2020') + expected = 63 + assert len(strict_df) == expected + assert isinstance(strict_df, pd.DataFrame) + +@pytest.mark.common +def test_constrain_on_ts(): + ts = time_serie(12, '01/01/2020', '01/05/2020') + strict_ts = constrain(ts, '01/01/2020', '01/04/2020') + expected = 63 + assert len(strict_ts) == expected + assert isinstance(strict_ts, pd.Series) diff --git a/tests/test_ts.py b/tests/test_ts.py index 9210586..65cc8fe 100644 --- a/tests/test_ts.py +++ b/tests/test_ts.py @@ -16,7 +16,19 @@ def test_ts_with_null_values(): ts = time_serie(21554, start="31/12/1992", end="01/06/2019") data = ts.loc['1994-04-01'] assert np.isnan(data) == True - + +@pytest.mark.ts +def test_time_serie_single_date(): + ts = time_serie(12, "17/08/2018") + assert len(ts) == 1 + assert ts.dtype == np.float + +@pytest.mark.ts +def test_time_serie_single_date_with_strict_as_true(): + ts = time_serie(12, "01/05/2018", strict=True) + assert len(ts) == 0 + assert ts.dtype == np.float + @pytest.mark.ts def test_ts_with_strict_as_false(): ts = time_serie(20577, "17/08/2019", "18/08/2019")