├── .github └── workflows │ ├── publish-to-pypi.yml │ └── tests.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile └── source │ ├── _static │ └── icon.png │ ├── conf.py │ ├── developer.rst │ ├── index.rst │ ├── install.rst │ └── quickstart.rst ├── icon.png ├── mypy.ini ├── pytest.ini ├── setup.cfg ├── setup.py ├── sgs ├── __init__.py ├── __version__.py ├── api.py ├── common.py ├── dataframe.py ├── metadata.py ├── search.py └── ts.py └── tests ├── __init__.py ├── test_api.py ├── test_common.py ├── test_dataframe.py ├── test_metadata.py ├── test_search.py └── test_ts.py /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release: 12 | name: Create Release 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@master 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: Release ${{ github.ref }} 25 | body: | 26 | Changes in this Release 27 | draft: false 28 | prerelease: false 29 | deploy: 30 | needs: release 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v1 34 | - name: Set up Python 35 | uses: actions/setup-python@v1 36 | with: 37 | python-version: '3.x' 38 | - name: Install dependencies 39 | run: | 40 | python -m pip install --upgrade pip 41 | pip install setuptools wheel 42 | - name: Build 43 | run: | 44 | python setup.py sdist bdist_wheel 45 | - name: Publish distribution 📦 to PyPI 46 | uses: pypa/gh-action-pypi-publish@release/v1 47 | with: 48 | user: __token__ 49 | password: ${{ secrets.pypi_password }} 50 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-22.04 8 | strategy: 9 | max-parallel: 4 10 | matrix: 11 | python-version: [3.9, 3.10.16, 3.11, 3.12, 3.13] 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set locale 16 | run: sudo apt-get update && sudo apt-get install tzdata locales -y && sudo locale-gen pt_BR.UTF-8 17 | 18 | - name: Setup Python 19 | uses: actions/setup-python@v1 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Install dependencies with pip 24 | run: | 25 | python -m pip install .[dev] 26 | - run: pytest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .cache/ 3 | .ipynb_checkpoints/ 4 | .pytest_cache/ 5 | .mypy_cache/ 6 | .vscode/ 7 | dist/ 8 | build/ 9 | sgs.egg-info/ 10 | notebooks/ 11 | env/ 12 | 13 | 14 | *.bat 15 | *.pyc 16 | *.swp 17 | *.egg 18 | *.sh 19 | 20 | .coverage 21 | MANIFEST 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.5 5 | - 3.6 6 | 7 | install: 8 | - pip install -e .[dev] 9 | 10 | script: 11 | - bandit sgs/ 12 | - pytest 13 | 14 | after_success: 15 | - codecov 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rafael Ribeiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/sgs.svg 2 | :target: https://pypi.org/project/sgs/ 3 | 4 | .. image:: https://img.shields.io/pypi/l/sgs.svg 5 | :target: https://pypi.org/project/sgs/ 6 | 7 | .. image:: https://img.shields.io/pypi/pyversions/sgs.svg 8 | :target: https://pypi.org/project/sgs/ 9 | 10 | .. image:: https://img.shields.io/pypi/dm/sgs.svg 11 | :target: https://pypi.org/project/sgs/ 12 | 13 | .. image:: https://github.com/rafpyprog/pySGS/actions/workflows/tests.yml/badge.svg 14 | :target: https://github.com/rafpyprog/pySGS/actions/workflows/tests.yml 15 | 16 | .. image:: https://img.shields.io/codecov/c/github/rafpyprog/pysgs.svg 17 | :target: https://codecov.io/github/rafpyprog/pysgs 18 | :alt: codecov.io 19 | 20 | .. image:: https://img.shields.io/readthedocs/pysgs.svg 21 | :target: https://pysgs.readthedocs.io/en/stable/ 22 | :alt: Read the docs! 23 | 24 | |pic 1| **SGS** 25 | ================= 26 | 27 | .. |pic 1| image:: https://raw.githubusercontent.com/rafpyprog/sgs/master/icon.png 28 | 29 | Introduction 30 | ------------ 31 | 32 | This library provides a pure Python interface for the Brazilian Central Bank's 33 | `Time Series Management System (SGS) `_ api. 34 | It works with Python 3.5 and above. 35 | 36 | SGS is a service with more than 18,000 time series with economical and financial information. 37 | This library is intended to make it easier for Python programmers to use this data in projects of 38 | any kind, providing mechanisms to search for, extract and join series. 39 | 40 | 41 | Quickstart 42 | ---------- 43 | Access time series data with **sgs** is very simple 44 | 45 | Begin by importing the ``sgs`` module: 46 | 47 | 48 | .. code-block:: python 49 | 50 | import sgs 51 | 52 | 53 | Now, let's try to get a time serie. For this example, let's get the 54 | "Interest rate - CDI" time serie in 2018, wich has the code 12. 55 | 56 | 57 | .. code-block:: python 58 | 59 | CDI_CODE = 12 60 | ts = sgs.time_serie(CDI_CODE, start='02/01/2018', end='31/12/2018') 61 | 62 | 63 | Now, we have a Pandas Series object called ``ts``, with all the data and 64 | the index representing the dates. 65 | 66 | .. code-block:: python 67 | 68 | ts.head() 69 | 70 | +------------+----------+ 71 | | 2018-01-02 | 0.026444 | 72 | +------------+----------+ 73 | | 2018-01-03 | 0.026444 | 74 | +------------+----------+ 75 | | 2018-01-04 | 0.026444 | 76 | +------------+----------+ 77 | | 2018-01-05 | 0.026444 | 78 | +------------+----------+ 79 | | 2018-01-08 | 0.026444 | 80 | +------------+----------+ 81 | 82 | Feature Suport 83 | -------------- 84 | 85 | * Get time serie data with an one-liner using ``sgs.time_serie`` 86 | * Create a dataframe from a list of time series codes with ``sgs.dataframe`` 87 | * Search time series by text or code with ``sgs.search_ts`` 88 | * Get metadata from all the series in a dataframe using ``sgs.metadata`` 89 | * Support to search and metadata in English and Portuguese 90 | * Automatic retry 91 | * Automatic cached requests 92 | 93 | 94 | Installation 95 | ------------ 96 | To install, simply use pip: 97 | 98 | .. code-block:: bash 99 | 100 | $ pip install sgs 101 | 102 | Documentation 103 | ------------- 104 | 105 | Complete documentation is available at https://pysgs.readthedocs.io/en/stable/. 106 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/source/_static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafpyprog/pySGS/a5618d0edecc9658fa914553bf863ba7be5f1336/docs/source/_static/icon.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from typing import List 4 | 5 | import sgs 6 | # -- Path setup -------------------------------------------------------------- 7 | 8 | # If extensions (or modules to document with autodoc) are in another directory, 9 | # add these directories to sys.path here. If the directory is relative to the 10 | # documentation root, use os.path.abspath to make it absolute, like shown here. 11 | # 12 | sys.path.insert(0, os.path.abspath('..')) 13 | 14 | # -- Project information ----------------------------------------------------- 15 | 16 | project = 'SGS' 17 | copyright = '2019, Rafael Alves Ribeiro' 18 | author = 'Rafael Alves Ribeiro' 19 | 20 | # The full version, including alpha/beta/rc tags 21 | version = sgs.__version__ 22 | release = sgs.__version__ 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | master_doc = 'index' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | "sphinx.ext.autodoc", 33 | "sphinx.ext.intersphinx", 34 | "sphinx.ext.todo", 35 | "sphinx.ext.viewcode", 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = [] # type: List[str] 45 | 46 | 47 | # -- Options for HTML output ------------------------------------------------- 48 | 49 | # The theme to use for HTML and HTML Help pages. See the documentation for 50 | # a list of builtin themes. 51 | # 52 | html_theme = 'alabaster' 53 | 54 | html_theme_options = { 55 | "show_powered_by": False, 56 | "github_user": "rafpyprog", 57 | "github_repo": "pySGS", 58 | "github_button": True, 59 | "github_banner": False, 60 | "github_type": "star", 61 | "show_related": False, 62 | "note_bg": "#FFF59C", 63 | } 64 | 65 | # Add any paths that contain custom static files (such as style sheets) here, 66 | # relative to this directory. They are copied after the builtin static files, 67 | # so a file named "default.css" will overwrite the builtin "default.css". 68 | html_static_path = ['_static'] 69 | 70 | 71 | html_logo = '_static/icon.png' 72 | -------------------------------------------------------------------------------- /docs/source/developer.rst: -------------------------------------------------------------------------------- 1 | Developer Interface 2 | ------------------- 3 | 4 | .. _pandas.Series: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.html 5 | .. _pandas.DataFrame: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html 6 | .. _list: https://docs.python.org/3/library/stdtypes.html#list 7 | 8 | Main Interface 9 | ============== 10 | 11 | .. module:: sgs 12 | 13 | All of PySGS functionality can be accessed by these 4 methods. 14 | 15 | .. autofunction:: time_serie 16 | .. autofunction:: dataframe 17 | .. autofunction:: search_ts(query: Union[int, str], language: str) -> Optional[list] 18 | .. autofunction:: metadata 19 | 20 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. SGS documentation master file, created by 2 | sphinx-quickstart on Tue Jun 25 23:55:09 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | SGS - Python API to Bacen Time Series Management System (SGS) 7 | =============================================================== 8 | 9 | 10 | .. image:: https://img.shields.io/pypi/v/sgs.svg 11 | :target: https://pypi.org/project/sgs/ 12 | 13 | .. image:: https://img.shields.io/pypi/l/sgs.svg 14 | :target: https://pypi.org/project/sgs/ 15 | 16 | .. image:: https://img.shields.io/pypi/pyversions/sgs.svg 17 | :target: https://pypi.org/project/sgs/ 18 | 19 | .. image:: https://img.shields.io/pypi/dm/sgs.svg 20 | :target: https://pypi.org/project/sgs/ 21 | 22 | .. image:: https://img.shields.io/travis/rafpyprog/pysgs.svg 23 | :target: https://travis-ci.org/rafpyprog/pySGS/ 24 | 25 | .. image:: https://img.shields.io/codecov/c/github/rafpyprog/pysgs.svg 26 | :target: https://codecov.io/github/rafpyprog/pysgs 27 | :alt: codecov.io 28 | 29 | 30 | Introduction 31 | ------------ 32 | This library provides a pure Python interface for the Brazilian Central Bank's 33 | `Time Series Management System (SGS) `_ api. 34 | It works with Python 3.5 and above. 35 | 36 | SGS is a service with more than 18,000 time series with economical and financial information. 37 | This library is intended to make it easier for Python programmers to use this data in projects of 38 | any kind, providing mechanisms to search for, extract and join series. 39 | 40 | 41 | The User Guide 42 | -------------- 43 | 44 | In this section we discuss some of the basic ways in which the package can be used. 45 | 46 | .. toctree:: 47 | :maxdepth: 2 48 | 49 | install 50 | quickstart 51 | 52 | 53 | The API Documentation / Guide 54 | ----------------------------- 55 | 56 | If you are looking for information on a specific function, class, or method, 57 | this part of the documentation is for you. 58 | 59 | .. toctree:: 60 | :maxdepth: 2 61 | 62 | developer 63 | 64 | -------------------------------------------------------------------------------- /docs/source/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installing SGS 4 | ============== 5 | 6 | pip install 7 | ----------- 8 | 9 | To install **sgs**, simply run this command in your terminal of choice:: 10 | 11 | $ pip install sgs 12 | 13 | 14 | Get the Source Code 15 | ------------------- 16 | 17 | **Sgs** is developed on GitHub, where the code is 18 | `always available `_. 19 | 20 | You can clone the public repository:: 21 | 22 | $ git clone git://github.com/rafpyprog/pySGS.git 23 | 24 | Once you have a copy of the source, you can embed it in your own Python 25 | package, or install it into your site-packages easily:: 26 | 27 | $ cd pySGS 28 | $ pip install . 29 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | Quickstart 4 | ========== 5 | 6 | Eager to get started? This page gives a good introduction in how to get started with **sgs**. 7 | 8 | First, make sure that: 9 | 10 | * Sgs is :ref:`installed ` 11 | 12 | Let’s get started with some simple examples. 13 | 14 | 15 | Time Serie 16 | ---------- 17 | Access time series data with **sgs** is very simple 18 | 19 | Begin by importing the ``sgs`` module:: 20 | 21 | >>> import sgs 22 | 23 | Now, let's try to get a time serie. For this example, let's get the 24 | "Interest rate - CDI" time serie in 2018, wich has the code 12. 25 | Don't worry, on the next steps we will learn how to search for time 26 | series codes:: 27 | 28 | >>> CDI_CODE = 12 29 | >>> ts = sgs.time_serie(CDI_CODE, start='02/01/2018', end='31/12/2018') 30 | 31 | Now, we have a Pandas Series object called ``ts``, with all the data and the index 32 | representing the dates. 33 | 34 | >>> ts.head() 35 | 2018-01-02 0.026444 36 | 2018-01-03 0.026444 37 | 2018-01-04 0.026444 38 | 2018-01-05 0.026444 39 | 2018-01-08 0.026444 40 | 41 | Dataframe 42 | --------- 43 | A common use case is building a dataframe with many time series. Once you have the desired time 44 | series codes, you can easily create a dataframe with a single line of code. PySGS will fetch the 45 | data and join the time series using the dates. Lets create a dataframe with two time series: 46 | 47 | >>> CDI = 12 48 | >>> INCC = 192 # National Index of Building Costs 49 | >>> df = sgs.dataframe([CDI, INCC], start='02/01/2018', end='31/12/2018') 50 | 51 | Now, we have a Pandas DataFrame object called ``df``, with all the data and the index 52 | representing the dates used to join the two time series. 53 | 54 | >>> df.head() 55 | 12 192 56 | 2018-01-01 NaN 0.31 57 | 2018-01-02 0.026444 NaN 58 | 2018-01-03 0.026444 NaN 59 | 2018-01-04 0.026444 NaN 60 | 2018-01-05 0.026444 NaN 61 | 62 | The ``NaN`` values are due to the fact that the INCC time serie frequency is monthly 63 | while CDI has a daily frequency. 64 | 65 | 66 | Searching 67 | --------- 68 | 69 | The SGS service provides thousands of time series. It's possible to search for time series by code and 70 | also by name, with support to queries in English and Portuguese. 71 | 72 | 73 | Search by name 74 | ~~~~~~~~~~~~~~ 75 | Let’s perform a search for time series with data about gold. 76 | 77 | * English 78 | 79 | >>> results = sgs.search_ts("gold", language="en") 80 | >>> print(len(results) 81 | 29 82 | >>> results[0] 83 | {'code': 4, 'name': 'BM&F Gold - gramme', 'unit': 'c.m.u.', 84 | 'frequency': 'D', 'first_value': Timestamp('1989-12-29 00:00:00'), 85 | 'last_value': Timestamp('2019-06-27 00:00:00'), 'source': 'BM&FBOVESPA'} 86 | 87 | * Portuguese 88 | 89 | >>> results = sgs.search_ts("ouro", language="pt") 90 | >>> print(len(results) 91 | 29 92 | >>> results[0] 93 | {'code': 4, 'name': 'Ouro BM&F - grama', 'unit': 'u.m.c.', 94 | 'frequency': 'D', 'first_value': Timestamp('1989-12-29 00:00:00'), 95 | 'last_value': Timestamp('2019-06-27 00:00:00'), 'source': 'BM&FBOVESPA'} 96 | 97 | 98 | Search by code 99 | ~~~~~~~~~~~~~~ 100 | If you already have the time serie's code, this may be usefull to get the metadata. 101 | 102 | >>> GOLD_BMF = 4 103 | >>> sgs.search_ts(GOLD_BMF, language="pt") 104 | [{'code': 4, 'name': 'Ouro BM&F - grama', 'unit': 'u.m.c.', 'frequency': 'D', 105 | 'first_value': Timestamp('1989-12-29 00:00:00'), 106 | 'last_value': Timestamp('2019-06-27 00:00:00'), 107 | 'source': 'BM&FBOVESPA'}] 108 | 109 | 110 | Metadata 111 | -------- 112 | 113 | To get the metadata about all the series present in a dataframe use the ``metadata`` function: 114 | 115 | >>> CDI = 12 116 | >>> INCC = 192 # National Index of Building Costs 117 | >>> df = sgs.dataframe([CDI, INCC], start='02/01/2018', end='31/12/2018') 118 | >>> sgs.metadata(df) 119 | [{'code': 12, 'name': 'Interest rate - CDI', 'unit': '% p.d.', 'frequency': 'D', 120 | 'first_value': Timestamp('1986-03-06 00:00:00'), 'last_value': Timestamp('2019-06-27 00:00:00'), 121 | 'source': 'Cetip'}, {'code': 192, 'name': 'National Index of Building Costs (INCC)', 122 | 'unit': 'Monthly % var.', 'frequency': 'M', 'first_value': Timestamp('1944-02-29 00:00:00'), 123 | 'last_value': Timestamp('2019-05-01 00:00:00'), 'source': 'FGV'}] 124 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafpyprog/pySGS/a5618d0edecc9658fa914553bf863ba7be5f1336/icon.png -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --ignore=docs -vv -x --mypy --cov=sgs 3 | 4 | markers = 5 | api 6 | common 7 | dataframe 8 | metadata 9 | pep8 10 | mypy 11 | search 12 | ts 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | __version__ = "3.0.0" 5 | 6 | 7 | with open('README.rst', 'r', encoding='utf-8') as f: 8 | readme = f.read() 9 | 10 | 11 | requirements = [ 12 | 'beautifulsoup4>=4.7.1', 13 | 'html5lib>=1.0.1', 14 | 'pandas>=0.24.2', 15 | 'retrying>=1.3.3', 16 | 'requests>=2.22.0', 17 | ] 18 | 19 | dev_requirements = [ 20 | "bandit>=1.6.0", 21 | "codecov>=2.0.15", 22 | "mypy>=0.910", 23 | "pytest>=4.6.2", 24 | "pytest-cov>=2.7.1", 25 | "pytest-mypy>=0.8.1", 26 | "types-requests>=2.25.6" 27 | ] 28 | 29 | setup( 30 | name='sgs', 31 | packages=['sgs'], 32 | python_requires=">=3.6", 33 | install_requires=requirements, 34 | extras_require={ 35 | 'dev': dev_requirements 36 | }, 37 | version=__version__, 38 | description=('Python wrapper para o webservice do SGS - ' 39 | 'Sistema Gerenciador de Series Temporais do ' 40 | 'Banco Central do Brasil.'), 41 | license="MIT", 42 | long_description=readme, 43 | author='Rafael Alves Ribeiro', 44 | author_email='rafael.alves.ribeiro@gmail.com', 45 | url='https://github.com/rafpyprog/pySGS', 46 | classifiers=[ 47 | 'Development Status :: 4 - Beta', 48 | 'Intended Audience :: Developers', 49 | 'Intended Audience :: Science/Research', 50 | 'License :: OSI Approved :: MIT License', 51 | 'Programming Language :: Python :: 3.9', 52 | 'Programming Language :: Python :: 3.10', 53 | 'Programming Language :: Python :: 3.11', 54 | 'Programming Language :: Python :: 3.12', 55 | 'Programming Language :: Python :: 3.13', 56 | 'Topic :: Scientific/Engineering :: Information Analysis', 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /sgs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Time Series Management System 3 | 4 | The SGS is a system maintened by the Central Bank of Brazil with the 5 | objective of consolidating and making available economic-financial 6 | information, as well as maintaining uniformity among documents 7 | produced based on time series stored in it. 8 | """ 9 | 10 | from .__version__ import __version__ 11 | from .dataframe import dataframe 12 | from .ts import time_serie 13 | from .metadata import metadata 14 | from .search import search_ts 15 | -------------------------------------------------------------------------------- /sgs/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.0.0" 2 | -------------------------------------------------------------------------------- /sgs/api.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import Union, List, Dict 3 | 4 | import pandas as pd 5 | import requests 6 | from retrying import retry 7 | 8 | from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER, to_datetime 9 | 10 | 11 | @retry(stop_max_attempt_number=MAX_ATTEMPT_NUMBER) 12 | @functools.lru_cache(maxsize=LRU_CACHE_SIZE) 13 | def get_data(ts_code: int, begin: str, end: str) -> List: 14 | """ 15 | Requests time series data from the SGS API in json format. 16 | """ 17 | 18 | url = ( 19 | "https://api.bcb.gov.br/dados/serie/bcdata.sgs.{}" 20 | "/dados?formato=json&dataInicial={}&dataFinal={}" 21 | ) 22 | request_url = url.format(ts_code, begin, end) 23 | response = requests.get(request_url) 24 | return response.json() 25 | 26 | def get_data_with_strict_range(ts_code: int, begin: str, end: str) -> List: 27 | 28 | """ 29 | Request time series data from the SGS API considering a strict range of dates. 30 | 31 | SGS API default behaviour returns the last stored value when selected date range have no data. 32 | 33 | It is possible to catch this behaviour when the first record date precedes the start date. 34 | 35 | This function enforces an empty data set when the first record date precedes the start date, avoiding records out of selected range. 36 | 37 | :param ts_code: time serie code. 38 | :param begin: start date (DD/MM/YYYY). 39 | :param end: end date (DD/MM/YYYY). 40 | 41 | :return: Data in json format or an empty list 42 | :rtype: list 43 | 44 | """ 45 | data = get_data(ts_code, begin, end) 46 | 47 | first_record_date = to_datetime(data[0]["data"], "pt") 48 | period_start_date = to_datetime(begin, 'pt') 49 | 50 | try: 51 | is_out_of_range = first_record_date < period_start_date #type: ignore 52 | if is_out_of_range: 53 | raise ValueError 54 | except TypeError: 55 | print("ERROR: Serie " + str(ts_code) + " - Please, use 'DD/MM/YYYY' format for date strings.") 56 | data = [] 57 | except ValueError: 58 | print("WARNING: Serie " + str(ts_code) + " - There is no data for the requested period, but there's previous data.") 59 | data = [] 60 | 61 | return data 62 | -------------------------------------------------------------------------------- /sgs/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared functions. 3 | """ 4 | import functools 5 | import locale 6 | import os 7 | import re 8 | from datetime import datetime 9 | from typing import Union 10 | 11 | 12 | LRU_CACHE_SIZE = 32 13 | MAX_ATTEMPT_NUMBER = 5 14 | 15 | 16 | @functools.lru_cache(maxsize=365) 17 | def to_datetime(date_string: str, language: str) -> Union[datetime, str]: 18 | """ Converts a date string to a datetime object """ 19 | 20 | yyyy = "[0-9]{4}" 21 | mmm_yyyy = r"[a-z]{3}/[0-9]{4}" 22 | 23 | if re.match(yyyy, date_string): 24 | date = datetime(int(date_string), 12, 31) 25 | elif re.match(mmm_yyyy, date_string): 26 | month_text, year_text = date_string.split("/") 27 | months = [ 28 | ("jan", "jan"), ("fev", "feb"), ("mar", "mar"), 29 | ("abr", "apr"), ("mai", "may"), ("jun", "jun"), 30 | ("jul", "jul"), ("ago", "aug"), ("set", "sep"), 31 | ("out", "oct"), ("nov", "nov"), ("dez", "dec") 32 | ] 33 | for n, month_names in enumerate(months, 1): 34 | if month_text in month_names: 35 | month_number = n 36 | break 37 | date = datetime(int(year_text), month_number, 1) 38 | else: 39 | try: 40 | day, month, year = [int(date_part) for date_part in date_string.split("/")] 41 | date = datetime(year, month, day) 42 | except ValueError: 43 | # return the original value if we cant parse it 44 | return date_string 45 | return date 46 | -------------------------------------------------------------------------------- /sgs/dataframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dataframe 3 | """ 4 | from typing import Dict, List, Tuple, Union 5 | 6 | import pandas as pd 7 | 8 | from . import api 9 | from . import search 10 | from .ts import time_serie 11 | 12 | 13 | def dataframe(ts_codes: Union[int, List, Tuple], start: str, end: str, strict: bool = False) -> pd.DataFrame: 14 | """ 15 | Creates a dataframe from a list of time serie codes. 16 | 17 | :param ts_codes: single code or list/tuple of time series codes. 18 | :param start: start date (DD/MM/YYYY). 19 | :param end: end date (DD/MM/YYYY). 20 | :param strict: boolean to enforce a strict date range. 21 | 22 | :return: Pandas dataframe. 23 | :rtype: pandas.DataFrame_ 24 | 25 | Usage:: 26 | 27 | >>> CDI = 12 28 | >>> INCC = 192 # National Index of Building Costs 29 | >>> df = sgs.dataframe([CDI, INCC], start='02/01/2018', end='31/12/2018') 30 | >>> df.head() 31 | 12 192 32 | 2018-01-01 NaN 0.31 33 | 2018-01-02 0.026444 NaN 34 | 2018-01-03 0.026444 NaN 35 | 2018-01-04 0.026444 NaN 36 | 2018-01-05 0.026444 NaN 37 | 38 | """ 39 | if isinstance(ts_codes, int): 40 | ts_codes = [ts_codes] 41 | 42 | series = [] 43 | for code in ts_codes: 44 | ts = time_serie(code, start, end, strict) 45 | series.append(ts) 46 | 47 | df = pd.concat(series, axis=1) 48 | return df 49 | -------------------------------------------------------------------------------- /sgs/metadata.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, List, Union 2 | 3 | import pandas as pd 4 | 5 | from .search import search_ts 6 | 7 | 8 | def metadata(ts_code: Union[int, pd.DataFrame], language: str = "en") -> Optional[List]: 9 | """Request metadata about a time serie or all time series in a pandas dataframe. 10 | 11 | :param ts_code: time serie code or pandas dataframe with time series as columns. 12 | :param language: language of the returned metadata. 13 | 14 | :return: List of dicts containing time series metadata. 15 | :rtype: list_ 16 | 17 | Usage:: 18 | 19 | >>> CDI = 12 20 | >>> INCC = 192 # National Index of Building Costs 21 | >>> df = sgs.dataframe([CDI, INCC], start='02/01/2018', end='31/12/2018') 22 | >>> sgs.metadata(df) 23 | [{'code': 12, 'name': 'Interest rate - CDI', 'unit': '% p.d.', 'frequency': 'D', 24 | 'first_value': Timestamp('1986-03-06 00:00:00'), 'last_value': Timestamp('2019-06-27 00:00:00'), 25 | 'source': 'Cetip'}, {'code': 192, 'name': 'National Index of Building Costs (INCC)', 26 | 'unit': 'Monthly % var.', 'frequency': 'M', 'first_value': Timestamp('1944-02-29 00:00:00'), 27 | 'last_value': Timestamp('2019-05-01 00:00:00'), 'source': 'FGV'}] 28 | """ 29 | info = [] 30 | if isinstance(ts_code, pd.core.frame.DataFrame): 31 | for col in ts_code.columns: 32 | col_info = search_ts(col, language) 33 | if col_info is not None: 34 | info.append(col_info[0]) 35 | else: 36 | info.append(None) 37 | else: 38 | col_info = search_ts(ts_code, language) 39 | info.append(col_info) 40 | return info 41 | -------------------------------------------------------------------------------- /sgs/search.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | import functools 3 | from typing import Union, Optional 4 | 5 | import requests 6 | from retrying import retry 7 | import pandas as pd 8 | 9 | from .common import LRU_CACHE_SIZE, MAX_ATTEMPT_NUMBER, to_datetime 10 | 11 | 12 | @unique 13 | class Language(Enum): 14 | pt = "pt" 15 | en = "en" 16 | 17 | 18 | @unique 19 | class SearchURL(Enum): 20 | pt = "https://www3.bcb.gov.br/sgspub/index.jsp?idIdioma=P" 21 | en = "https://www3.bcb.gov.br/sgspub/" 22 | 23 | 24 | @unique 25 | class SearchMethod(Enum): 26 | code = "localizarSeriesPorCodigo" 27 | text = "localizarSeriesPorTexto" 28 | 29 | 30 | @unique 31 | class Columns(Enum): 32 | pt = { 33 | "start": "Início dd/MM/aaaa", 34 | "last": "Últ. valor", 35 | "code": "Cód.", 36 | "frequency": "Per.", 37 | "name": "Nome completo", 38 | "source": "Fonte", 39 | "unit": "Unid.", 40 | } 41 | 42 | en = { 43 | "start": "Start dd/MM/yyyy", 44 | "last": "Last value", 45 | "code": "Code", 46 | "frequency": "Per.", 47 | "name": "Full name", 48 | "source": "Source", 49 | "unit": "Unit", 50 | } 51 | 52 | 53 | def init_search_session(language: str) -> requests.Session: 54 | """ 55 | Starts a session on SGS and get cookies requesting the initial page. 56 | 57 | Parameters 58 | 59 | language: str, "en" or "pt" 60 | Language used for search and results. 61 | """ 62 | session = requests.Session() 63 | search_url = SearchURL[language].value 64 | session.get(search_url) 65 | return session 66 | 67 | 68 | def parse_search_response(response, language: str) -> Optional[list]: 69 | HTML = response.text 70 | 71 | not_found_msgs = ("No series found", "Nenhuma série localizada") 72 | if any(msg in HTML for msg in not_found_msgs): 73 | return None 74 | 75 | cols = Columns[language].value 76 | START = cols["start"] 77 | LAST = cols["last"] 78 | 79 | try: 80 | df = pd.read_html(HTML, attrs={"id": "tabelaSeries"}, flavor="html5lib", skiprows=1)[0] 81 | df[START] = df[START].map(lambda x: to_datetime(str(x), language)) 82 | df[LAST] = df[LAST].map(lambda x: to_datetime(str(x), language)) 83 | col_names = { 84 | cols["code"]: "code", 85 | cols["name"]: "name", 86 | cols["frequency"]: "frequency", 87 | cols["unit"]: "unit", 88 | cols["start"]: "first_value", 89 | cols["last"]: "last_value", 90 | cols["source"]: "source", 91 | } 92 | df.rename(columns=col_names, inplace=True) 93 | cols_names = [ 94 | "code", 95 | "name", 96 | "unit", 97 | "frequency", 98 | "first_value", 99 | "last_value", 100 | "source", 101 | ] # type: ignore 102 | df = df[cols_names] 103 | except (IndexError, KeyError): 104 | return None 105 | else: 106 | return df.to_dict(orient="records") 107 | 108 | 109 | @retry(stop_max_attempt_number=MAX_ATTEMPT_NUMBER) 110 | @functools.lru_cache(maxsize=32) 111 | def search_ts(query: Union[int, str], language: str) -> Optional[list]: 112 | """Search for time series and return metadata about it. 113 | 114 | :param query: code(int) or name(str) used to search for a time serie. 115 | :param language: string (en or pt) used in query and return results. 116 | 117 | :return: List of results matching the search query. 118 | :rtype: list_ 119 | 120 | Usage:: 121 | 122 | >>> results = sgs.search_ts("gold", language="en") 123 | >>> len(results) 124 | 29 125 | >>> results[0] 126 | {'code': 4, 'name': 'BM&F Gold - gramme', 'unit': 'c.m.u.', 127 | 'frequency': 'D', 'first_value': Timestamp('1989-12-29 00:00:00'), 128 | 'last_value': Timestamp('2019-06-27 00:00:00'), 'source': 'BM&FBOVESPA'} 129 | """ 130 | 131 | session = init_search_session(language) 132 | URL = "https://www3.bcb.gov.br/sgspub/localizarseries/" "localizarSeries.do" 133 | 134 | if isinstance(query, int): 135 | search_method = SearchMethod.code 136 | elif isinstance(query, str): 137 | search_method = SearchMethod.text 138 | else: 139 | raise ValueError("query must be an int or str: ({})".format(query)) 140 | 141 | url = URL.format(search_method.value) 142 | 143 | params = { 144 | "method": search_method.value, 145 | "periodicidade": 0, 146 | "codigo": None, 147 | "fonte": 341, 148 | "texto": None, 149 | "hdFiltro": None, 150 | "hdOidGrupoSelecionado": None, 151 | "hdSeqGrupoSelecionado": None, 152 | "hdNomeGrupoSelecionado": None, 153 | "hdTipoPesquisa": 4, 154 | "hdTipoOrdenacao": 0, 155 | "hdNumPagina": None, 156 | "hdPeriodicidade": "Todas", 157 | "hdSeriesMarcadas": None, 158 | "hdMarcarTodos": None, 159 | "hdFonte": None, 160 | "hdOidSerieMetadados": None, 161 | "hdNumeracao": None, 162 | "hdOidSeriesLocalizadas": None, 163 | "linkRetorno": "/sgspub/consultarvalores/telaCvsSelecionarSeries.paint", 164 | "linkCriarFiltros": "/sgspub/manterfiltros/telaMfsCriarFiltro.paint", 165 | } 166 | 167 | if search_method == SearchMethod.code: 168 | params["codigo"] = query 169 | else: 170 | params["texto"] = query 171 | params["hdTipoPesquisa"] = 6 172 | 173 | response = session.post(url, params=params, timeout=10) # type: ignore 174 | response.raise_for_status() 175 | 176 | results = parse_search_response(response, language) 177 | 178 | session.close() 179 | 180 | return results 181 | -------------------------------------------------------------------------------- /sgs/ts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Time Serie manipulation 3 | """ 4 | from typing import Dict, List, Optional 5 | 6 | import numpy as np 7 | import pandas as pd 8 | 9 | from . import api 10 | from . import search 11 | from .common import to_datetime 12 | 13 | 14 | def time_serie(ts_code: int, start: str, end: str, strict: bool = False) -> pd.Series: 15 | """ 16 | Request a time serie data. 17 | 18 | :param ts_code: time serie code. 19 | :param start: start date (DD/MM/YYYY). 20 | :param end: end date (DD/MM/YYYY). 21 | :param strict: boolean to enforce a strict date range. 22 | 23 | :return: Time serie values as pandas Series indexed by date. 24 | :rtype: pandas.Series_ 25 | 26 | Usage:: 27 | 28 | >>> CDI = 12 29 | >>> ts = sgs.time_serie(CDI, start='02/01/2018', end='31/12/2018') 30 | >>> ts.head() 31 | 2018-01-02 0.026444 32 | 2018-01-03 0.026444 33 | 2018-01-04 0.026444 34 | 2018-01-05 0.026444 35 | 2018-01-08 0.026444 36 | """ 37 | 38 | if strict: 39 | ts_data = api.get_data_with_strict_range(ts_code, start, end) 40 | else: 41 | ts_data = api.get_data(ts_code, start, end) 42 | 43 | values = [] 44 | index = [] 45 | for i in ts_data: 46 | values.append(i["valor"]) 47 | index.append(to_datetime(i["data"], "pt")) 48 | 49 | # Transform empty strings in null values 50 | values = [np.nan if value == "" else value for value in values] 51 | return pd.Series(values, index, name=ts_code, dtype=np.float64) # type: ignore 52 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rafpyprog/pySGS/a5618d0edecc9658fa914553bf863ba7be5f1336/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from sgs import api 4 | import pandas as pd 5 | 6 | 7 | @pytest.mark.api 8 | def test_get_data(): 9 | NUMBER_OF_LINES = 20 10 | data = api.get_data(4, "02/01/2018", "31/01/2018") 11 | assert isinstance(data, list) 12 | assert len(data) == NUMBER_OF_LINES 13 | 14 | 15 | @pytest.mark.api 16 | def test_get_data_with_strict_range(): 17 | NUMBER_OF_LINES = 0 18 | data = api.get_data_with_strict_range(20577, "17/08/2019", "18/08/2019") 19 | assert isinstance(data, list) 20 | assert len(data) == NUMBER_OF_LINES 21 | 22 | -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import pytest 4 | from sgs.common import to_datetime 5 | 6 | 7 | @pytest.mark.common 8 | @pytest.mark.parametrize("language", ["pt", "en"]) 9 | def test_to_datetime_dd_mm_aaaaa(language): 10 | date_string = "01/01/2018" 11 | expected = datetime(day=1, month=1, year=2018) 12 | assert to_datetime(date_string, language) == expected 13 | 14 | 15 | @pytest.mark.common 16 | @pytest.mark.parametrize( 17 | "date_string,language", [("mai/2018", "pt"), ("may/2018", "en")] 18 | ) 19 | def test_to_datetime_mmm_aaaaa(date_string, language): 20 | expected = datetime(day=1, month=5, year=2018) 21 | assert to_datetime(date_string, language) == expected 22 | 23 | 24 | @pytest.mark.common 25 | def test_to_datetime_aaaaa(): 26 | expected = datetime(day=31, month=12, year=2018) 27 | date_string = '2018' 28 | assert to_datetime(date_string, 'pt') == expected 29 | -------------------------------------------------------------------------------- /tests/test_dataframe.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from sgs.dataframe import dataframe 3 | 4 | 5 | @pytest.mark.dataframe 6 | def test_dataframe_one_ts(): 7 | df = dataframe(4, start="02/01/2018", end="31/01/2018") 8 | assert df.shape == (20, 1) 9 | 10 | 11 | @pytest.mark.dataframe 12 | def test_dataframe_multiple_ts(): 13 | ts_codes = [12, 433] 14 | df = dataframe(ts_codes, start="02/01/2018", end="31/01/2018") 15 | assert df.shape == (23, 2) 16 | 17 | @pytest.mark.dataframe 18 | def test_dataframe_one_with_strict_as_false(): 19 | df = dataframe(20577, start='17/08/2019', end='18/08/2019') 20 | assert df.shape == (1, 1) 21 | 22 | @pytest.mark.dataframe 23 | def test_dataframe_one_with_strict_as_true(): 24 | df = dataframe(20577, start='17/08/2019', end='18/08/2019', strict=True) 25 | assert df.shape == (0, 1) 26 | 27 | @pytest.mark.dataframe 28 | def test_dataframe_multiple_with_strict_as_false(): 29 | ts_codes = [20577,20669] 30 | df = dataframe(ts_codes, start='17/08/2019', end='18/08/2019') 31 | assert df.shape == (1, 2) 32 | 33 | @pytest.mark.dataframe 34 | def test_dataframe_multiple_with_strict_as_true(): 35 | ts_codes = [20577,20669] 36 | df = dataframe(ts_codes, start='17/08/2019', end='18/08/2019', strict=True) 37 | assert df.shape == (0, 2) 38 | -------------------------------------------------------------------------------- /tests/test_metadata.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from sgs.metadata import * 4 | from sgs import dataframe 5 | 6 | 7 | @pytest.mark.metadata 8 | def test_metadata_returns_list_with_int_as_parameter(): 9 | assert isinstance(metadata(4), list) 10 | 11 | 12 | @pytest.mark.metadata 13 | def test_metadata_returns_list_with_df_as_parameter(): 14 | ts_codes = [12, 433] 15 | df = dataframe(ts_codes, start="02/01/2018", end="31/01/2018") 16 | meta = metadata(df) 17 | assert isinstance(meta, list) 18 | assert len(meta) == len(ts_codes) 19 | -------------------------------------------------------------------------------- /tests/test_search.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import requests 3 | 4 | from sgs.search import * 5 | from sgs.common import to_datetime 6 | 7 | @pytest.mark.search 8 | @pytest.mark.parametrize("language", ["en", "pt"]) 9 | def test_init_search_session(language): 10 | session = init_search_session(language) 11 | assert isinstance(session, requests.Session) 12 | 13 | 14 | @pytest.mark.search 15 | def test_search_by_code_english(): 16 | code = 4 17 | results = search_ts(code, Language.en.value) 18 | print(results) 19 | metadata = results[0] 20 | assert metadata['name'] == "BM&F Gold - gramme" 21 | assert metadata['first_value'] == to_datetime("29/12/1989", "en") 22 | assert metadata['frequency'] == "D" 23 | 24 | 25 | @pytest.mark.search 26 | def test_search_by_code_portuguese(): 27 | code = 4 28 | results = search_ts(code, Language.pt.value) 29 | metadata = results[0] 30 | assert metadata['name'] == "Ouro BM&F - grama" 31 | assert metadata['first_value'] == to_datetime("29/12/1989", "pt") 32 | assert metadata['frequency'] == "D" 33 | 34 | 35 | @pytest.mark.search 36 | @pytest.mark.parametrize( 37 | "query,language,expected", 38 | [ 39 | ( 40 | 4, 41 | "pt", 42 | { 43 | "name": "Ouro BM&F - grama", 44 | "first_value": to_datetime("29/12/1989", "pt"), 45 | "freq": "D", 46 | }, 47 | ), 48 | ( 49 | 4, 50 | "en", 51 | { 52 | "name": "BM&F Gold - gramme", 53 | "first_value": to_datetime("29/12/1989", "en"), 54 | "freq": "D", 55 | }, 56 | ), 57 | ( 58 | 28209, 59 | "pt", 60 | { 61 | "name": ( 62 | "Saldo de títulos de dívida emitidos por " 63 | "empresas e famílias - títulos privados" 64 | ), 65 | "first_value": to_datetime("01/01/2013", "pt"), 66 | "freq": "M", 67 | }, 68 | ), 69 | ], 70 | ) 71 | def test_search_by_code(query, language, expected): 72 | results = search_ts(query, language) 73 | assert isinstance(results, list) 74 | results = results[0] 75 | assert results["frequency"] == expected["freq"] 76 | assert results["name"] == expected["name"] 77 | first_value = expected["first_value"] 78 | assert results["first_value"] == first_value 79 | 80 | 81 | @pytest.mark.search 82 | def test_search_by_text(): 83 | results = search_ts("Ouro BM$F - grama", "pt") 84 | assert isinstance(results, list) 85 | assert len(results) == 1 86 | 87 | # portuguese query and english language returns None 88 | results = search_ts("Ouro BM$F - grama", "en") 89 | assert results is None 90 | 91 | 92 | @pytest.mark.search 93 | def test_search_by_text_query_returns_multiple_result_pages(): 94 | results = search_ts("produto", "pt") 95 | assert isinstance(results, list) 96 | assert len(results) == 50 97 | 98 | 99 | @pytest.mark.search 100 | def test_search_by_text_multiple_results(): 101 | results = search_ts("dolar", "pt") 102 | assert isinstance(results, list) 103 | result_count = 43 104 | assert len(results) == result_count 105 | assert results[0]["code"] == 1 106 | assert results[-1]["code"] == 29372 107 | -------------------------------------------------------------------------------- /tests/test_ts.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from sgs.ts import time_serie 5 | 6 | 7 | @pytest.mark.ts 8 | def test_time_serie(): 9 | ts = time_serie(4, "02/01/2018", "31/01/2018") 10 | assert len(ts) == 20 11 | assert ts.dtype == np.float64 12 | 13 | @pytest.mark.ts 14 | def test_ts_with_null_values(): 15 | # Issue #28 16 | ts = time_serie(21554, start="31/12/1992", end="01/06/2019") 17 | data = ts.loc['1994-04-01'] 18 | assert np.isnan(data) == True 19 | 20 | @pytest.mark.ts 21 | def test_ts_with_strict_as_false(): 22 | ts = time_serie(20577, "17/08/2019", "18/08/2019") 23 | assert len(ts) == 1 24 | assert ts.dtype == np.float64 25 | 26 | @pytest.mark.ts 27 | def test_ts_with_strict_as_true(): 28 | ts = time_serie(20577, "17/08/2019", "18/08/2019", True) 29 | assert len(ts) == 0 30 | assert ts.dtype == np.float64 31 | --------------------------------------------------------------------------------