pax_global_header00006660000000000000000000000064134656463170014531gustar00rootroot0000000000000052 comment=5975e4bf2a9cb4a5e19518f57a6934e90b6dbb0b chrillux-brottsplatskartan-5975e4b/000077500000000000000000000000001346564631700175115ustar00rootroot00000000000000chrillux-brottsplatskartan-5975e4b/.gitignore000066400000000000000000000023201346564631700214760ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # lab file bpk.py .vscode/ chrillux-brottsplatskartan-5975e4b/.travis.yml000066400000000000000000000001631346564631700216220ustar00rootroot00000000000000cache: directories: - $HOME/.cache/pip language: python python: "3.6" install: "pip install tox" script: tox chrillux-brottsplatskartan-5975e4b/LICENSE000066400000000000000000000020621346564631700205160ustar00rootroot00000000000000MIT License Copyright (c) 2018 Christian Biamont Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. chrillux-brottsplatskartan-5975e4b/README.md000066400000000000000000000022151346564631700207700ustar00rootroot00000000000000# brottsplatskartan [![Build Status](https://travis-ci.com/chrillux/brottsplatskartan.svg?branch=master)](https://travis-ci.com/chrillux/brottsplatskartan) A simple API wrapper for [Brottsplatskartan](https://brottsplatskartan.se) ## Install `pip install brottsplatskartan` ## Usage ```python import brottsplatskartan b1 = brottsplatskartan.BrottsplatsKartan(app=app, areas=areas) b2 = brottsplatskartan.BrottsplatsKartan(app=app, longitude=longitude, latitude=latitude) incidents = b1.get_incidents() for incident_area in incidents: for incident in incidents[incident_area]: print(incident) incidents = b2.get_incidents() for incident_area in incidents: for incident in incidents[incident_area]: print(incident) ``` app: unique app value, see https://brottsplatskartan.se/sida/api areas = Python list of valid areas, see https://brottsplatskartan.se/api/areas longitude = Longitude latitude = Latitude If setting "areas" parameter, longitude and latitude will be ignored. ## Development Pull requests welcome. Must pass `tox` and include tests. ## Disclaimer Not affiliated with brottsplatskartan.se. Use at your own risk. chrillux-brottsplatskartan-5975e4b/brottsplatskartan/000077500000000000000000000000001346564631700232735ustar00rootroot00000000000000chrillux-brottsplatskartan-5975e4b/brottsplatskartan/__init__.py000066400000000000000000000112471346564631700254110ustar00rootroot00000000000000# coding=utf-8 """ Brottsplatskartan API """ import datetime import time from json.decoder import JSONDecodeError from typing import Union import requests AREAS = [ "Blekinge län", "Dalarnas län", "Gotlands län", "Gävleborgs län", "Hallands län", "Jämtlands län", "Jönköpings län", "Kalmar län", "Kronobergs län", "Norrbottens län", "Skåne län", "Stockholms län", "Södermanlands län", "Uppsala län", "Värmlands län", "Västerbottens län", "Västernorrlands län", "Västmanlands län", "Västra Götalands län", "Örebro län", "Östergötlands län" ] ATTRIBUTION = "Information provided by brottsplatskartan.se" BROTTS_URL = "https://brottsplatskartan.se/api" class BrottsplatsKartan: # pylint: disable=too-few-public-methods """ Brottsplatskartan API wrapper. """ def __init__(self, app='bpk', areas=None, longitude=None, latitude=None): """ Setup initial brottsplatskartan configuration. """ self.parameters = {"app": app} self.incidents = {} if areas: for area in areas: if area not in AREAS: raise ValueError('not a valid area: {}'.format(area)) self.url = BROTTS_URL + "/events" self.parameters["areas"] = areas elif longitude and latitude: self.url = BROTTS_URL + "/eventsNearby" self.parameters["lat"] = latitude self.parameters["lng"] = longitude else: # Missing parameters. Using default values. self.url = BROTTS_URL + "/events" self.parameters["areas"] = ["Stockholms län"] @staticmethod def _get_datetime_as_ymd(date: time.struct_time) -> datetime.datetime: datetime_ymd = datetime.datetime(date.tm_year, date.tm_mon, date.tm_mday) return datetime_ymd @staticmethod def is_ratelimited(requests_response) -> bool: """ Check if we have been ratelimited. """ rate_limited = requests_response.headers.get('x-ratelimit-reset') if rate_limited: print("You have been rate limited until " + time.strftime( '%Y-%m-%d %H:%M:%S%z', time.localtime(int(rate_limited)))) return True return False def get_incidents_from_bpk(self, parameters) -> Union[list, bool]: """ Make the API calls to get incidents """ brotts_entries_left = True incidents_today = [] url = self.url while brotts_entries_left: requests_response = requests.get(url, params=parameters) if self.is_ratelimited(requests_response): return False try: requests_response = requests_response.json() except JSONDecodeError: print("got JSONDecodeError") return False incidents = requests_response.get("data") if not incidents: incidents_today = [] break datetime_today = datetime.date.today() datetime_today_as_time = time.strptime(str(datetime_today), "%Y-%m-%d") today_date_ymd = self._get_datetime_as_ymd(datetime_today_as_time) for incident in incidents: incident_pubdate = incident["pubdate_iso8601"] incident_date = time.strptime(incident_pubdate, "%Y-%m-%dT%H:%M:%S%z") incident_date_ymd = self._get_datetime_as_ymd(incident_date) if today_date_ymd == incident_date_ymd: incidents_today.append(incident) else: brotts_entries_left = False break if requests_response.get("links"): url = requests_response["links"]["next_page_url"] else: break return incidents_today def get_incidents(self) -> Union[list, bool]: """ Get today's incidents. """ areas = self.parameters.get("areas") all_incidents = {} current_incidents = [] if areas: parameters = {} for area in areas: parameters["app"] = self.parameters.get("app") parameters["area"] = area current_incidents = self.get_incidents_from_bpk(parameters) all_incidents.update({area: current_incidents}) else: current_incidents = self.get_incidents_from_bpk(self.parameters) all_incidents.update({"latlng": current_incidents}) if current_incidents is False: return False return all_incidents chrillux-brottsplatskartan-5975e4b/setup.py000066400000000000000000000011251346564631700212220ustar00rootroot00000000000000"""Setup Brottsplatskartan API wrapper""" from setuptools import find_packages, setup setup( name='brottsplatskartan', version='1.0.5', description='Simple API wrapper to brottsplatskartan.se.', url='https://github.com/chrillux/brottsplatskartan', license='MIT', author='chrillux', author_email='christianbiamont@gmail.com', packages=find_packages(), install_requires=['requests>=2.20.0'], classifiers=[ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', ] ) chrillux-brottsplatskartan-5975e4b/tests/000077500000000000000000000000001346564631700206535ustar00rootroot00000000000000chrillux-brottsplatskartan-5975e4b/tests/test_brottsplatskartan.py000066400000000000000000000063531346564631700260550ustar00rootroot00000000000000import datetime import time import unittest from unittest.mock import Mock, patch import brottsplatskartan BROTTS_URL = "https://brottsplatskartan.se/api" class TestBrottsplatskartan(unittest.TestCase): @staticmethod def _get_incident_data(): datetime_today = datetime.date.today() datetime_today_str = str(datetime_today) datetime_today_str_full = datetime_today_str + "T10:00:00+0200" timedelta = datetime.timedelta(days=1) tomorrow = datetime_today + timedelta tomorrow_str = str(tomorrow) tomorrow_str_full = tomorrow_str + "T10:00:00+0200" data = {'data': [{'id': 1234, 'pubdate_iso8601': datetime_today_str_full, 'title_type': 'Inbrott', 'description': 'A description of the crime.' }, {'id': 2345, 'pubdate_iso8601': tomorrow_str_full, 'title_type': 'Brand', 'description': 'Another description' } ] } return data def test_no_parameters(self): b = brottsplatskartan.BrottsplatsKartan() self.assertEqual(b.url, BROTTS_URL + "/events") self.assertEqual(b.parameters.get('areas'), ['Stockholms län']) self.assertEqual(b.parameters.get('app'), 'bpk') def test_areas_parameter(self): b = brottsplatskartan.BrottsplatsKartan( areas=["Stockholms län"] ) self.assertEqual(b.parameters.get('areas'), ['Stockholms län']) self.assertEqual(b.url, BROTTS_URL + "/events") def test_non_valid_areas_parameter(self): self.assertRaises(ValueError, brottsplatskartan.BrottsplatsKartan, areas=["Nonexistent län"]) def test_long_lat_parameters(self): b = brottsplatskartan.BrottsplatsKartan( longitude=58.22, latitude=10.01, ) self.assertEqual(b.parameters.get('lng'), 58.22) self.assertEqual(b.parameters.get('lat'), 10.01) self.assertEqual(b.url, BROTTS_URL + "/eventsNearby") def test_get_ymd_as_datetime_from_time_strptime(self): a_test_date = time.strptime("2018-11-20", "%Y-%m-%d") ymd = brottsplatskartan.\ BrottsplatsKartan._get_datetime_as_ymd(a_test_date) self.assertIsInstance(ymd, datetime.datetime) @patch('brottsplatskartan.requests.get') def test_get_incidents_rate_limited(self, mock_requests): mock_requests.return_value = Mock(headers={ 'x-ratelimit-reset': time.time()}) b = brottsplatskartan.BrottsplatsKartan() incidents = b.get_incidents() self.assertFalse(incidents) @patch('brottsplatskartan.requests.get') def test_get_incidents(self, mock_requests): data = self._get_incident_data() mock_requests.return_value = Mock(headers={'no': 'headers'}) mock_requests.return_value.json.return_value = data b = brottsplatskartan.BrottsplatsKartan() incidents = b.get_incidents() self.assertEqual(len(incidents), 1) if __name__ == '__main__': unittest.main() chrillux-brottsplatskartan-5975e4b/tox.ini000066400000000000000000000010031346564631700210160ustar00rootroot00000000000000# content of: tox.ini , put in same dir as setup.py [tox] envlist = py36, lint skip_missing_interpreters = True [tool:unittest] testpaths = tests [testenv] ignore_errors = True changedir = tests commands = py.test -v --cov-report term-missing --cov brottsplatskartan deps = pytest pytest-cov [testenv:lint] ignore_errors = True changedir = tests commands = flake8 pylint --output-format=colorized brottsplatskartan pydocstyle brottsplatskartan deps = flake8 pylint pydocstyle