Merge where I left backend-frameworkification #2
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -3,6 +3,8 @@ build/ | ||||
| dist/ | ||||
| tags/ | ||||
| *.egg-info/ | ||||
| .tox/ | ||||
| .coverage | ||||
| dr.botzo.data | ||||
| dr.botzo.cfg | ||||
| localsettings.py | ||||
|  | ||||
| @ -1,20 +0,0 @@ | ||||
| doc-warnings: true | ||||
| strictness: high | ||||
| ignore-paths: | ||||
|   - migrations | ||||
| ignore-patterns: | ||||
|   - \.log$ | ||||
|   - localsettings.py$ | ||||
|   - parsetab.py$ | ||||
| pylint: | ||||
|   enable: | ||||
|     - relative-import | ||||
|   options: | ||||
|     max-line-length: 120 | ||||
|   good-names: log | ||||
| pep8: | ||||
|   options: | ||||
|     max-line-length: 120 | ||||
| pep257: | ||||
|   disable: | ||||
|     - D203 | ||||
| @ -22,7 +22,8 @@ class Countdown(Plugin): | ||||
|     new_reminder_regex = (r'remind\s+(?P<who>[^\s]+)\s+(?P<when_type>at|in|on)\s+(?P<when>.*?)\s+' | ||||
|                           r'(and\s+every\s+(?P<recurring_period>.*?)\s+)?' | ||||
|                           r'(until\s+(?P<recurring_until>.*?)\s+)?' | ||||
|                           r'(to|that|about)\s+(?P<text>.*)') | ||||
|                           r'(to|that|about)\s+(?P<text>.*?)' | ||||
|                           r'(?=\s+\("(?P<name>.*)"\)|$)') | ||||
| 
 | ||||
|     def __init__(self, bot, connection, event): | ||||
|         """Initialize some stuff.""" | ||||
| @ -42,7 +43,7 @@ class Countdown(Plugin): | ||||
| 
 | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+list$', | ||||
|                                                          self.handle_item_list, -20) | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+(\S+)$', | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!countdown\s+(.+)$', | ||||
|                                                          self.handle_item_detail, -20) | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], self.new_reminder_regex, | ||||
|                                                          self.handle_new_reminder, -50) | ||||
| @ -99,9 +100,16 @@ class Countdown(Plugin): | ||||
|             recurring_period = match.group('recurring_period') | ||||
|             recurring_until = match.group('recurring_until') | ||||
|             text = match.group('text') | ||||
|             name = match.group('name') | ||||
|             log.debug("%s / %s / %s", who, when, text) | ||||
| 
 | ||||
|             item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s')) | ||||
|             if not name: | ||||
|                 item_name = '{0:s}-{1:s}'.format(event.sender_nick, timezone.now().strftime('%s')) | ||||
|             else: | ||||
|                 if CountdownItem.objects.filter(name=name).count() > 0: | ||||
|                     self.bot.reply(event, "item with name '{0:s}' already exists".format(name)) | ||||
|                     return 'NO MORE' | ||||
|                 item_name = name | ||||
| 
 | ||||
|             # parse when to send the notification | ||||
|             if when_type == 'in': | ||||
|  | ||||
							
								
								
									
										23
									
								
								countdown/migrations/0006_auto_20201025_1716.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								countdown/migrations/0006_auto_20201025_1716.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.1.2 on 2020-10-25 17:16 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('countdown', '0005_countdownitem_recurring_until'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='countdownitem', | ||||
|             name='recurring_period', | ||||
|             field=models.CharField(blank=True, default='', max_length=64), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name='countdownitem', | ||||
|             name='reminder_target', | ||||
|             field=models.CharField(blank=True, default='', max_length=64), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,14 +1,8 @@ | ||||
| """Countdown item models.""" | ||||
| 
 | ||||
| import logging | ||||
| 
 | ||||
| from django.db import models | ||||
| from django.utils import timezone | ||||
| 
 | ||||
| 
 | ||||
| log = logging.getLogger('countdown.models') | ||||
| 
 | ||||
| 
 | ||||
| class CountdownItem(models.Model): | ||||
|     """Track points in time.""" | ||||
| 
 | ||||
| @ -19,13 +13,13 @@ class CountdownItem(models.Model): | ||||
|     sent_reminder = models.BooleanField(default=False) | ||||
| 
 | ||||
|     reminder_message = models.TextField(default="") | ||||
|     reminder_target = models.CharField(max_length=64, default='') | ||||
|     reminder_target = models.CharField(max_length=64, blank=True, default='') | ||||
| 
 | ||||
|     recurring_period = models.CharField(max_length=64, default='') | ||||
|     recurring_period = models.CharField(max_length=64, blank=True, default='') | ||||
|     recurring_until = models.DateTimeField(null=True, blank=True, default=None) | ||||
| 
 | ||||
|     created_time = models.DateTimeField(auto_now_add=True) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """String representation.""" | ||||
|         """Summarize object.""" | ||||
|         return "{0:s} @ {1:s}".format(self.name, timezone.localtime(self.at_time).strftime('%Y-%m-%d %H:%M:%S %Z')) | ||||
|  | ||||
							
								
								
									
										14
									
								
								countdown/serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								countdown/serializers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| """REST serializers for countdown items.""" | ||||
| from rest_framework import serializers | ||||
| 
 | ||||
| from countdown.models import CountdownItem | ||||
| 
 | ||||
| 
 | ||||
| class CountdownItemSerializer(serializers.ModelSerializer): | ||||
|     """Countdown item serializer for the REST API.""" | ||||
| 
 | ||||
|     class Meta: | ||||
|         """Meta options.""" | ||||
| 
 | ||||
|         model = CountdownItem | ||||
|         fields = '__all__' | ||||
							
								
								
									
										12
									
								
								countdown/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								countdown/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| """URL patterns for the countdown views.""" | ||||
| from django.conf.urls import include, url | ||||
| from rest_framework.routers import DefaultRouter | ||||
| 
 | ||||
| from countdown.views import CountdownItemViewSet | ||||
| 
 | ||||
| router = DefaultRouter() | ||||
| router.register(r'items', CountdownItemViewSet) | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     url(r'^api/', include(router.urls)), | ||||
| ] | ||||
							
								
								
									
										16
									
								
								countdown/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								countdown/views.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| """Provide an interface to countdown items.""" | ||||
| # from rest_framework.decorators import action | ||||
| # from rest_framework.response import Response | ||||
| from rest_framework.permissions import IsAuthenticated | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| 
 | ||||
| from countdown.models import CountdownItem | ||||
| from countdown.serializers import CountdownItemSerializer | ||||
| 
 | ||||
| 
 | ||||
| class CountdownItemViewSet(ReadOnlyModelViewSet): | ||||
|     """Provide list and detail actions for countdown items.""" | ||||
| 
 | ||||
|     queryset = CountdownItem.objects.all() | ||||
|     serializer_class = CountdownItemSerializer | ||||
|     permission_classes = [IsAuthenticated] | ||||
| @ -14,11 +14,12 @@ logger = logging.getLogger(__name__) | ||||
| class Dice(Plugin): | ||||
|     """Roll simple or complex dice strings.""" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|     def __init__(self, bot, connection, event): | ||||
|         """Set up the plugin.""" | ||||
|         super(Dice, self).__init__() | ||||
|         self.roller = DiceRoller() | ||||
| 
 | ||||
|         super(Dice, self).__init__(bot, connection, event) | ||||
| 
 | ||||
|     def start(self): | ||||
|         """Set up the handlers.""" | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!roll\s+(.*)$', | ||||
|  | ||||
| @ -1,11 +1,9 @@ | ||||
| """General/baselite/site-wide URLs.""" | ||||
| 
 | ||||
| from adminplus.sites import AdminSitePlus | ||||
| from django.conf.urls import include, url | ||||
| from django.contrib import admin | ||||
| from django.views.generic import TemplateView | ||||
| 
 | ||||
| from adminplus.sites import AdminSitePlus | ||||
| 
 | ||||
| admin.site = AdminSitePlus() | ||||
| admin.sites.site = admin.site | ||||
| admin.autodiscover() | ||||
| @ -13,11 +11,13 @@ admin.autodiscover() | ||||
| urlpatterns = [ | ||||
|     url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'), | ||||
| 
 | ||||
|     url(r'^countdown/', include('countdown.urls')), | ||||
|     url(r'^dice/', include('dice.urls')), | ||||
|     url(r'^dispatch/', include('dispatch.urls')), | ||||
|     url(r'^itemsets/', include('facts.urls')), | ||||
|     url(r'^karma/', include('karma.urls')), | ||||
|     url(r'^markov/', include('markov.urls')), | ||||
|     url(r'^pi/', include('pi.urls')), | ||||
|     url(r'^races/', include('races.urls')), | ||||
|     url(r'^weather/', include('weather.urls')), | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ import re | ||||
| import irc.client | ||||
| 
 | ||||
| from django.conf import settings | ||||
| from django.db.models import Count, Sum | ||||
| 
 | ||||
| from ircbot.lib import Plugin | ||||
| from karma.models import KarmaKey, KarmaLogEntry | ||||
| @ -23,6 +24,9 @@ class Karma(Plugin): | ||||
|         self.connection.add_global_handler('pubmsg', self.handle_chatter, -20) | ||||
|         self.connection.add_global_handler('privmsg', self.handle_chatter, -20) | ||||
| 
 | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], | ||||
|                                                          (r'^!karma\s+keyreport\s+(.*)'), | ||||
|                                                          self.handle_keyreport, -20) | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], | ||||
|                                                          r'^!karma\s+rank\s+(.*)$', | ||||
|                                                          self.handle_rank, -20) | ||||
| @ -42,6 +46,7 @@ class Karma(Plugin): | ||||
|         self.connection.remove_global_handler('pubmsg', self.handle_chatter) | ||||
|         self.connection.remove_global_handler('privmsg', self.handle_chatter) | ||||
| 
 | ||||
|         self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_keyreport) | ||||
|         self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_rank) | ||||
|         self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_report) | ||||
|         self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_stats) | ||||
| @ -102,6 +107,19 @@ class Karma(Plugin): | ||||
|         except KarmaKey.DoesNotExist: | ||||
|             return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1))) | ||||
| 
 | ||||
|     def handle_keyreport(self, connection, event, match): | ||||
|         """Provide report on a karma key.""" | ||||
|         key = match.group(1).lower().rstrip() | ||||
|         try: | ||||
|             karma_key = KarmaKey.objects.get(key=key) | ||||
|             karmaers = KarmaLogEntry.objects.filter(key=karma_key) | ||||
|             karmaers = karmaers.values('nickmask').annotate(Sum('delta')).annotate(Count('delta')).order_by('-delta__count') | ||||
|             karmaers_list =  [f"{irc.client.NickMask(x['nickmask']).nick} ({x['delta__count']}, {'+' if x['delta__sum'] >= 0 else ''}{x['delta__sum']})" for x in karmaers] | ||||
|             karmaers_list_str = ", ".join(karmaers_list[:10]) | ||||
|             return self.bot.reply(event, f"most opinionated on {key}: {karmaers_list_str}") | ||||
|         except KarmaKey.DoesNotExist: | ||||
|             return self.bot.reply(event, "i have not seen any karma for {0:s}".format(match.group(1))) | ||||
| 
 | ||||
|     def handle_report(self, connection, event, match): | ||||
|         """Provide some karma reports.""" | ||||
| 
 | ||||
|  | ||||
| @ -1,21 +1,14 @@ | ||||
| # coding: utf-8 | ||||
| 
 | ||||
| import logging | ||||
| 
 | ||||
| """Provide pi simulation results to IRC.""" | ||||
| from ircbot.lib import Plugin | ||||
| from pi.models import PiLog | ||||
| 
 | ||||
| 
 | ||||
| log = logging.getLogger('pi.ircplugin') | ||||
| 
 | ||||
| 
 | ||||
| class Pi(Plugin): | ||||
| 
 | ||||
|     """Use the Monte Carlo method to simulate pi.""" | ||||
| 
 | ||||
|     def start(self): | ||||
|         """Set up the handlers.""" | ||||
| 
 | ||||
|         self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!pi$', | ||||
|                                                          self.handle_pi, -20) | ||||
| 
 | ||||
| @ -23,17 +16,16 @@ class Pi(Plugin): | ||||
| 
 | ||||
|     def stop(self): | ||||
|         """Tear down handlers.""" | ||||
| 
 | ||||
|         self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_pi) | ||||
| 
 | ||||
|         super(Pi, self).stop() | ||||
| 
 | ||||
|     def handle_pi(self, connection, event, match): | ||||
|         """Handle the pi command by generating another value and presenting it.""" | ||||
| 
 | ||||
|         newest, x, y, hit = PiLog.objects.simulate() | ||||
|         newest, x, y = PiLog.objects.simulate() | ||||
|         msg = ("({0:.10f}, {1:.10f}) is {2}within the unit circle. π is {5:.10f}. (i:{3:d} p:{4:d})" | ||||
|                "".format(x, y, "" if hit else "not ", newest.count_inside, newest.count_total, newest.value())) | ||||
|                "".format(x, y, "" if newest.hit else "not ", newest.total_count_inside, | ||||
|                          newest.total_count, newest.value)) | ||||
| 
 | ||||
|         return self.bot.reply(event, msg) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										23
									
								
								pi/migrations/0003_rename_count_fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pi/migrations/0003_rename_count_fields.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.1.2 on 2020-10-24 16:27 | ||||
| 
 | ||||
| from django.db import migrations | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('pi', '0002_auto_20150521_2204'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.RenameField( | ||||
|             model_name='pilog', | ||||
|             old_name='count_total', | ||||
|             new_name='total_count', | ||||
|         ), | ||||
|         migrations.RenameField( | ||||
|             model_name='pilog', | ||||
|             old_name='count_inside', | ||||
|             new_name='total_count_inside', | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										25
									
								
								pi/migrations/0004_simulation_x_y_logging.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								pi/migrations/0004_simulation_x_y_logging.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| # Generated by Django 3.1.2 on 2020-10-24 16:31 | ||||
| 
 | ||||
| from django.db import migrations, models | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('pi', '0003_rename_count_fields'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='pilog', | ||||
|             name='simulation_x', | ||||
|             field=models.DecimalField(decimal_places=10, default=-1.0, max_digits=11), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='pilog', | ||||
|             name='simulation_y', | ||||
|             field=models.DecimalField(decimal_places=10, default=-1.0, max_digits=11), | ||||
|             preserve_default=False, | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										55
									
								
								pi/models.py
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								pi/models.py
									
									
									
									
									
								
							| @ -1,67 +1,70 @@ | ||||
| """Karma logging models.""" | ||||
| 
 | ||||
| import logging | ||||
| import math | ||||
| import pytz | ||||
| import random | ||||
| 
 | ||||
| import pytz | ||||
| from django.conf import settings | ||||
| from django.db import models | ||||
| 
 | ||||
| 
 | ||||
| log = logging.getLogger('pi.models') | ||||
| 
 | ||||
| 
 | ||||
| class PiLogManager(models.Manager): | ||||
| 
 | ||||
|     """Assemble some queries against PiLog.""" | ||||
| 
 | ||||
|     def simulate(self): | ||||
|         """Add one more entry to the log, and return it.""" | ||||
| 
 | ||||
|         try: | ||||
|             latest = self.latest() | ||||
|         except PiLog.DoesNotExist: | ||||
|             latest = PiLog(count_inside=0, count_total=0) | ||||
|             latest = PiLog.objects.create(simulation_x=0.0, simulation_y=0.0, | ||||
|                                           total_count_inside=0, total_count=0) | ||||
|             latest.save() | ||||
| 
 | ||||
|         inside = latest.count_inside | ||||
|         total = latest.count_total | ||||
|         inside = latest.total_count_inside | ||||
|         total = latest.total_count | ||||
| 
 | ||||
|         x = random.random() | ||||
|         y = random.random() | ||||
|         hit = True if math.hypot(x,y) < 1 else False | ||||
|         total += 1 | ||||
|         if math.hypot(x, y) < 1: | ||||
|             inside += 1 | ||||
| 
 | ||||
|         if hit: | ||||
|             newest = PiLog(count_inside=inside+1, count_total=total+1) | ||||
|         else: | ||||
|             newest = PiLog(count_inside=inside, count_total=total+1) | ||||
|         newest.save() | ||||
|         newest = PiLog.objects.create(simulation_x=x, simulation_y=y, | ||||
|                                       total_count_inside=inside, total_count=total) | ||||
| 
 | ||||
|         return newest, x, y, hit | ||||
|         # TODO: remove the x, y return values, now that we track them in the object | ||||
|         return newest, x, y | ||||
| 
 | ||||
| 
 | ||||
| class PiLog(models.Model): | ||||
| 
 | ||||
|     """Track pi as it is estimated over time.""" | ||||
| 
 | ||||
|     count_inside = models.PositiveIntegerField() | ||||
|     count_total = models.PositiveIntegerField() | ||||
|     simulation_x = models.DecimalField(max_digits=11, decimal_places=10) | ||||
|     simulation_y = models.DecimalField(max_digits=11, decimal_places=10) | ||||
|     total_count_inside = models.PositiveIntegerField() | ||||
|     total_count = models.PositiveIntegerField() | ||||
|     created = models.DateTimeField(auto_now_add=True) | ||||
| 
 | ||||
|     objects = PiLogManager() | ||||
| 
 | ||||
|     class Meta: | ||||
|         """Options for the PiLog class.""" | ||||
| 
 | ||||
|         get_latest_by = 'created' | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         """String representation.""" | ||||
| 
 | ||||
|         """Provide string representation.""" | ||||
|         tz = pytz.timezone(settings.TIME_ZONE) | ||||
|         return "({0:d}/{1:d}) @ {2:s}".format(self.count_inside, self.count_total, | ||||
|         return "({0:d}/{1:d}) @ {2:s}".format(self.total_count_inside, self.total_count, | ||||
|                                               self.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z')) | ||||
| 
 | ||||
|     @property | ||||
|     def value(self): | ||||
|         """Return this log entry's value of pi.""" | ||||
|         """Return this log entry's estimated value of pi.""" | ||||
|         if self.total_count == 0: | ||||
|             return 0.0 | ||||
|         return 4.0 * int(self.total_count_inside) / int(self.total_count) | ||||
| 
 | ||||
|         return 4.0 * int(self.count_inside) / int(self.count_total) | ||||
|     @property | ||||
|     def hit(self): | ||||
|         """Return if this log entry is inside the unit circle.""" | ||||
|         return math.hypot(self.simulation_x, self.simulation_y) < 1 | ||||
|  | ||||
							
								
								
									
										14
									
								
								pi/serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								pi/serializers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| """REST serializers for pi simulations.""" | ||||
| from rest_framework import serializers | ||||
| 
 | ||||
| from pi.models import PiLog | ||||
| 
 | ||||
| 
 | ||||
| class PiLogSerializer(serializers.ModelSerializer): | ||||
|     """Pi simulation log entry serializer for the REST API.""" | ||||
| 
 | ||||
|     class Meta: | ||||
|         """Meta options.""" | ||||
| 
 | ||||
|         model = PiLog | ||||
|         fields = ('id', 'simulation_x', 'simulation_y', 'total_count', 'total_count_inside', 'value', 'hit') | ||||
							
								
								
									
										12
									
								
								pi/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pi/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| """URL patterns for the pi views.""" | ||||
| from django.conf.urls import include, url | ||||
| from rest_framework.routers import DefaultRouter | ||||
| 
 | ||||
| from pi.views import PiLogViewSet | ||||
| 
 | ||||
| router = DefaultRouter() | ||||
| router.register(r'simulations', PiLogViewSet) | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     url(r'^api/', include(router.urls)), | ||||
| ] | ||||
							
								
								
									
										22
									
								
								pi/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pi/views.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| """Provide pi simulation results.""" | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.permissions import IsAuthenticated | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| 
 | ||||
| from pi.models import PiLog | ||||
| from pi.serializers import PiLogSerializer | ||||
| 
 | ||||
| 
 | ||||
| class PiLogViewSet(ReadOnlyModelViewSet): | ||||
|     """Provide list and detail actions for pi simulation log entries.""" | ||||
| 
 | ||||
|     queryset = PiLog.objects.all() | ||||
|     serializer_class = PiLogSerializer | ||||
|     permission_classes = [IsAuthenticated] | ||||
| 
 | ||||
|     @action(detail=False, methods=['post']) | ||||
|     def simulate(self, request): | ||||
|         """Run one simulation of the pi estimator.""" | ||||
|         simulation, _, _ = PiLog.objects.simulate() | ||||
|         return Response(self.get_serializer(simulation).data, 201) | ||||
| @ -1,6 +0,0 @@ | ||||
| -r requirements.in | ||||
| 
 | ||||
| logilab-common                  # prospector thing, i guess | ||||
| pip-tools                       # pip-compile | ||||
| prospector                      # code quality | ||||
| versioneer                      # auto-generate version numbers | ||||
| @ -1,68 +0,0 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile | ||||
| # To update, run: | ||||
| # | ||||
| #    pip-compile --output-file=requirements-dev.txt requirements-dev.in | ||||
| # | ||||
| astroid==2.2.5            # via pylint, pylint-celery, pylint-flask, requirements-detector | ||||
| certifi==2019.6.16        # via requests | ||||
| chardet==3.0.4            # via requests | ||||
| click==7.0                # via pip-tools | ||||
| django-adminplus==0.5 | ||||
| django-bootstrap3==11.0.0 | ||||
| django-extensions==2.1.9 | ||||
| django-registration-redux==2.6 | ||||
| django==2.2.2 | ||||
| djangorestframework==3.9.4 | ||||
| dodgy==0.1.9              # via prospector | ||||
| future==0.17.1            # via parsedatetime | ||||
| idna==2.8                 # via requests | ||||
| inflect==2.1.0            # via jaraco.itertools | ||||
| irc==15.0.6 | ||||
| isort==4.3.20             # via pylint | ||||
| jaraco.classes==2.0       # via jaraco.collections | ||||
| jaraco.collections==2.0   # via irc | ||||
| jaraco.functools==2.0     # via irc, jaraco.text, tempora | ||||
| jaraco.itertools==4.4.2   # via irc | ||||
| jaraco.logging==2.0       # via irc | ||||
| jaraco.stream==2.0        # via irc | ||||
| jaraco.text==3.0          # via irc, jaraco.collections | ||||
| lazy-object-proxy==1.4.1  # via astroid | ||||
| logilab-common==1.4.2 | ||||
| mccabe==0.6.1             # via prospector, pylint | ||||
| more-itertools==7.0.0     # via irc, jaraco.functools, jaraco.itertools | ||||
| oauthlib==3.0.1           # via requests-oauthlib | ||||
| parsedatetime==2.4 | ||||
| pep8-naming==0.4.1        # via prospector | ||||
| pip-tools==4.1.0 | ||||
| ply==3.11 | ||||
| prospector==1.1.6.4 | ||||
| pycodestyle==2.4.0        # via prospector | ||||
| pydocstyle==3.0.0         # via prospector | ||||
| pyflakes==1.6.0           # via prospector | ||||
| pylint-celery==0.3        # via prospector | ||||
| pylint-django==2.0.9      # via prospector | ||||
| pylint-flask==0.6         # via prospector | ||||
| pylint-plugin-utils==0.5  # via prospector, pylint-celery, pylint-django, pylint-flask | ||||
| pylint==2.3.1             # via prospector, pylint-celery, pylint-django, pylint-flask, pylint-plugin-utils | ||||
| python-dateutil==2.8.0 | ||||
| python-gitlab==1.9.0 | ||||
| python-mpd2==1.0.0 | ||||
| pytz==2019.1 | ||||
| pyyaml==5.1.1             # via prospector | ||||
| requests-oauthlib==1.2.0  # via twython | ||||
| requests==2.22.0          # via python-gitlab, requests-oauthlib, twython | ||||
| requirements-detector==0.6  # via prospector | ||||
| setoptconf==0.2.0         # via prospector | ||||
| six==1.12.0               # via astroid, django-extensions, irc, jaraco.classes, jaraco.collections, jaraco.itertools, jaraco.logging, jaraco.stream, logilab-common, pip-tools, pydocstyle, python-dateutil, python-gitlab, tempora | ||||
| snowballstemmer==1.2.1    # via pydocstyle | ||||
| sqlparse==0.3.0           # via django | ||||
| tempora==1.14.1           # via irc, jaraco.logging | ||||
| twython==3.7.0 | ||||
| typed-ast==1.4.0          # via astroid | ||||
| urllib3==1.25.3           # via requests | ||||
| versioneer==0.18 | ||||
| wrapt==1.11.2             # via astroid | ||||
| 
 | ||||
| # The following packages are considered to be unsafe in a requirements file: | ||||
| # setuptools==41.4.0        # via logilab-common | ||||
| @ -1,40 +0,0 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile | ||||
| # To update, run: | ||||
| # | ||||
| #    pip-compile --output-file=requirements.txt requirements.in | ||||
| # | ||||
| certifi==2019.6.16        # via requests | ||||
| chardet==3.0.4            # via requests | ||||
| django-adminplus==0.5 | ||||
| django-bootstrap3==11.0.0 | ||||
| django-extensions==2.1.9 | ||||
| django-registration-redux==2.6 | ||||
| django==2.2.2 | ||||
| djangorestframework==3.9.4 | ||||
| future==0.17.1            # via parsedatetime | ||||
| idna==2.8                 # via requests | ||||
| inflect==2.1.0            # via jaraco.itertools | ||||
| irc==15.0.6 | ||||
| jaraco.classes==2.0       # via jaraco.collections | ||||
| jaraco.collections==2.0   # via irc | ||||
| jaraco.functools==2.0     # via irc, jaraco.text, tempora | ||||
| jaraco.itertools==4.4.2   # via irc | ||||
| jaraco.logging==2.0       # via irc | ||||
| jaraco.stream==2.0        # via irc | ||||
| jaraco.text==3.0          # via irc, jaraco.collections | ||||
| more-itertools==7.0.0     # via irc, jaraco.functools, jaraco.itertools | ||||
| oauthlib==3.0.1           # via requests-oauthlib | ||||
| parsedatetime==2.4 | ||||
| ply==3.11 | ||||
| python-dateutil==2.8.0 | ||||
| python-gitlab==1.9.0 | ||||
| python-mpd2==1.0.0 | ||||
| pytz==2019.1 | ||||
| requests-oauthlib==1.2.0  # via twython | ||||
| requests==2.22.0          # via python-gitlab, requests-oauthlib, twython | ||||
| six==1.12.0               # via django-extensions, irc, jaraco.classes, jaraco.collections, jaraco.itertools, jaraco.logging, jaraco.stream, python-dateutil, python-gitlab, tempora | ||||
| sqlparse==0.3.0           # via django | ||||
| tempora==1.14.1           # via irc, jaraco.logging | ||||
| twython==3.7.0 | ||||
| urllib3==1.25.3           # via requests | ||||
							
								
								
									
										25
									
								
								requirements/requirements-dev.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								requirements/requirements-dev.in
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| -r requirements.in | ||||
| 
 | ||||
| # testing runner, test reporting, packages used during testing (e.g. requests-mock), etc. | ||||
| pytest | ||||
| pytest-cov | ||||
| pytest-django | ||||
| 
 | ||||
| # linting and other static code analysis | ||||
| bandit | ||||
| dlint | ||||
| flake8                      # flake8 and plugins, for local dev linting in vim | ||||
| flake8-blind-except | ||||
| flake8-builtins | ||||
| flake8-docstrings | ||||
| flake8-executable | ||||
| flake8-fixme | ||||
| flake8-isort | ||||
| flake8-logging-format | ||||
| flake8-mutable | ||||
| 
 | ||||
| # maintenance utilities and tox | ||||
| pip-tools                   # pip-compile | ||||
| tox                         # CI stuff | ||||
| tox-wheel                   # build wheels in tox | ||||
| versioneer                  # automatic version numbering | ||||
							
								
								
									
										92
									
								
								requirements/requirements-dev.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								requirements/requirements-dev.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile | ||||
| # To update, run: | ||||
| # | ||||
| #    pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in | ||||
| # | ||||
| appdirs==1.4.4            # via virtualenv | ||||
| asgiref==3.2.10           # via django | ||||
| attrs==20.2.0             # via pytest | ||||
| bandit==1.6.2             # via -r requirements/requirements-dev.in | ||||
| certifi==2020.6.20        # via requests | ||||
| chardet==3.0.4            # via requests | ||||
| click==7.1.2              # via pip-tools | ||||
| coverage==5.3             # via pytest-cov | ||||
| distlib==0.3.1            # via virtualenv | ||||
| django-adminplus==0.5     # via -r requirements/requirements.in | ||||
| django-bootstrap3==14.2.0  # via -r requirements/requirements.in | ||||
| django-extensions==3.0.9  # via -r requirements/requirements.in | ||||
| django-registration-redux==2.8  # via -r requirements/requirements.in | ||||
| django==3.1.2             # via -r requirements/requirements.in, django-bootstrap3, djangorestframework | ||||
| djangorestframework==3.12.1  # via -r requirements/requirements.in | ||||
| dlint==0.10.3             # via -r requirements/requirements-dev.in | ||||
| filelock==3.0.12          # via tox, virtualenv | ||||
| flake8-blind-except==0.1.1  # via -r requirements/requirements-dev.in | ||||
| flake8-builtins==1.5.3    # via -r requirements/requirements-dev.in | ||||
| flake8-docstrings==1.5.0  # via -r requirements/requirements-dev.in | ||||
| flake8-executable==2.0.4  # via -r requirements/requirements-dev.in | ||||
| flake8-fixme==1.1.1       # via -r requirements/requirements-dev.in | ||||
| flake8-isort==4.0.0       # via -r requirements/requirements-dev.in | ||||
| flake8-logging-format==0.6.0  # via -r requirements/requirements-dev.in | ||||
| flake8-mutable==1.2.0     # via -r requirements/requirements-dev.in | ||||
| flake8==3.8.4             # via -r requirements/requirements-dev.in, dlint, flake8-builtins, flake8-docstrings, flake8-executable, flake8-isort, flake8-mutable | ||||
| gitdb==4.0.5              # via gitpython | ||||
| gitpython==3.1.11         # via bandit | ||||
| idna==2.10                # via requests | ||||
| importlib-metadata==1.7.0  # via django-bootstrap3, flake8, inflect, pluggy, pytest, stevedore, tox, virtualenv | ||||
| importlib-resources==3.1.1  # via jaraco.text, virtualenv | ||||
| inflect==4.1.0            # via jaraco.itertools | ||||
| iniconfig==1.1.1          # via pytest | ||||
| irc==15.0.6               # via -r requirements/requirements.in | ||||
| isort==5.6.4              # via flake8-isort | ||||
| jaraco.classes==3.1.0     # via jaraco.collections | ||||
| jaraco.collections==3.0.0  # via irc | ||||
| jaraco.functools==3.0.1   # via irc, jaraco.text, tempora | ||||
| jaraco.itertools==5.0.0   # via irc | ||||
| jaraco.logging==3.0.0     # via irc | ||||
| jaraco.stream==3.0.0      # via irc | ||||
| jaraco.text==3.2.0        # via irc, jaraco.collections | ||||
| mccabe==0.6.1             # via flake8 | ||||
| more-itertools==8.5.0     # via irc, jaraco.classes, jaraco.functools, jaraco.itertools | ||||
| oauthlib==3.1.0           # via requests-oauthlib | ||||
| packaging==20.4           # via pytest, tox | ||||
| parsedatetime==2.6        # via -r requirements/requirements.in | ||||
| pbr==5.5.1                # via stevedore | ||||
| pip-tools==5.3.1          # via -r requirements/requirements-dev.in | ||||
| pluggy==0.13.1            # via pytest, tox | ||||
| ply==3.11                 # via -r requirements/requirements.in | ||||
| py==1.9.0                 # via pytest, tox | ||||
| pycodestyle==2.6.0        # via flake8 | ||||
| pydocstyle==5.1.1         # via flake8-docstrings | ||||
| pyflakes==2.2.0           # via flake8 | ||||
| pyparsing==2.4.7          # via packaging | ||||
| pytest-cov==2.10.1        # via -r requirements/requirements-dev.in | ||||
| pytest-django==4.1.0      # via -r requirements/requirements-dev.in | ||||
| pytest==6.1.1             # via -r requirements/requirements-dev.in, pytest-cov, pytest-django | ||||
| python-dateutil==2.8.1    # via -r requirements/requirements.in | ||||
| python-gitlab==2.5.0      # via -r requirements/requirements.in | ||||
| python-mpd2==1.1.0        # via -r requirements/requirements.in | ||||
| pytz==2020.1              # via -r requirements/requirements.in, django, irc, tempora | ||||
| pyyaml==5.3.1             # via bandit | ||||
| requests-oauthlib==1.3.0  # via twython | ||||
| requests==2.24.0          # via python-gitlab, requests-oauthlib, twython | ||||
| six==1.15.0               # via bandit, irc, jaraco.collections, jaraco.logging, jaraco.text, packaging, pip-tools, python-dateutil, tox, virtualenv | ||||
| smmap==3.0.4              # via gitdb | ||||
| snowballstemmer==2.0.0    # via pydocstyle | ||||
| sqlparse==0.4.1           # via django | ||||
| stevedore==3.2.2          # via bandit | ||||
| tempora==4.0.0            # via irc, jaraco.logging | ||||
| testfixtures==6.15.0      # via flake8-isort | ||||
| toml==0.10.1              # via pytest, tox | ||||
| tox-wheel==0.5.0          # via -r requirements/requirements-dev.in | ||||
| tox==3.20.1               # via -r requirements/requirements-dev.in, tox-wheel | ||||
| twython==3.8.2            # via -r requirements/requirements.in | ||||
| urllib3==1.25.11          # via requests | ||||
| versioneer==0.18          # via -r requirements/requirements-dev.in | ||||
| virtualenv==20.0.35       # via tox | ||||
| wheel==0.35.1             # via tox-wheel | ||||
| zipp==3.3.2               # via importlib-metadata, importlib-resources | ||||
| 
 | ||||
| # The following packages are considered to be unsafe in a requirements file: | ||||
| # pip | ||||
| # setuptools | ||||
							
								
								
									
										43
									
								
								requirements/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								requirements/requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile | ||||
| # To update, run: | ||||
| # | ||||
| #    pip-compile --output-file=requirements/requirements.txt requirements/requirements.in | ||||
| # | ||||
| asgiref==3.2.10           # via django | ||||
| certifi==2020.6.20        # via requests | ||||
| chardet==3.0.4            # via requests | ||||
| django-adminplus==0.5     # via -r requirements/requirements.in | ||||
| django-bootstrap3==14.2.0  # via -r requirements/requirements.in | ||||
| django-extensions==3.0.9  # via -r requirements/requirements.in | ||||
| django-registration-redux==2.8  # via -r requirements/requirements.in | ||||
| django==3.1.2             # via -r requirements/requirements.in, django-bootstrap3, djangorestframework | ||||
| djangorestframework==3.12.1  # via -r requirements/requirements.in | ||||
| idna==2.10                # via requests | ||||
| importlib-metadata==1.7.0  # via django-bootstrap3, inflect | ||||
| importlib-resources==3.1.1  # via jaraco.text | ||||
| inflect==4.1.0            # via jaraco.itertools | ||||
| irc==15.0.6               # via -r requirements/requirements.in | ||||
| jaraco.classes==3.1.0     # via jaraco.collections | ||||
| jaraco.collections==3.0.0  # via irc | ||||
| jaraco.functools==3.0.1   # via irc, jaraco.text, tempora | ||||
| jaraco.itertools==5.0.0   # via irc | ||||
| jaraco.logging==3.0.0     # via irc | ||||
| jaraco.stream==3.0.0      # via irc | ||||
| jaraco.text==3.2.0        # via irc, jaraco.collections | ||||
| more-itertools==8.5.0     # via irc, jaraco.classes, jaraco.functools, jaraco.itertools | ||||
| oauthlib==3.1.0           # via requests-oauthlib | ||||
| parsedatetime==2.6        # via -r requirements/requirements.in | ||||
| ply==3.11                 # via -r requirements/requirements.in | ||||
| python-dateutil==2.8.1    # via -r requirements/requirements.in | ||||
| python-gitlab==2.5.0      # via -r requirements/requirements.in | ||||
| python-mpd2==1.1.0        # via -r requirements/requirements.in | ||||
| pytz==2020.1              # via -r requirements/requirements.in, django, irc, tempora | ||||
| requests-oauthlib==1.3.0  # via twython | ||||
| requests==2.24.0          # via python-gitlab, requests-oauthlib, twython | ||||
| six==1.15.0               # via irc, jaraco.collections, jaraco.logging, jaraco.text, python-dateutil | ||||
| sqlparse==0.4.1           # via django | ||||
| tempora==4.0.0            # via irc, jaraco.logging | ||||
| twython==3.8.2            # via -r requirements/requirements.in | ||||
| urllib3==1.25.11          # via requests | ||||
| zipp==3.3.2               # via importlib-metadata, importlib-resources | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @ -8,7 +8,7 @@ HERE = os.path.dirname(os.path.abspath(__file__)) | ||||
| 
 | ||||
| 
 | ||||
| def extract_requires(): | ||||
|     with open(os.path.join(HERE, 'requirements.in'), 'r') as reqs: | ||||
|     with open(os.path.join(HERE, 'requirements/requirements.in'), 'r') as reqs: | ||||
|         return [line.split(' ')[0] for line in reqs if not line[0] == '-'] | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										47
									
								
								tests/test_pi_models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/test_pi_models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| """Test the pi models.""" | ||||
| from unittest import mock | ||||
| 
 | ||||
| from django.test import TestCase | ||||
| from django.utils.timezone import now | ||||
| 
 | ||||
| from pi.models import PiLog | ||||
| 
 | ||||
| 
 | ||||
| class PiLogTest(TestCase): | ||||
|     """Test pi models.""" | ||||
| 
 | ||||
|     def test_hit_calculation(self): | ||||
|         """Test that x,y combinations are properly considered inside or outside the circle.""" | ||||
|         hit_item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=0, total_count_inside=0) | ||||
|         miss_item = PiLog(simulation_x=1.0, simulation_y=1.0, total_count=0, total_count_inside=0) | ||||
| 
 | ||||
|         self.assertTrue(hit_item.hit) | ||||
|         self.assertFalse(miss_item.hit) | ||||
| 
 | ||||
|     def test_value_calculation(self): | ||||
|         """Test that a simulation's value of pi can be calculated.""" | ||||
|         item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=1000, total_count_inside=788) | ||||
|         zero_item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=0, total_count_inside=0) | ||||
| 
 | ||||
|         self.assertEqual(item.value, 3.152) | ||||
|         self.assertEqual(zero_item.value, 0.0) | ||||
| 
 | ||||
|     def test_string_repr(self): | ||||
|         """Test the string repr of a simulation log entry.""" | ||||
|         item = PiLog(simulation_x=0.0, simulation_y=0.0, total_count=1000, total_count_inside=788, | ||||
|                      created=now()) | ||||
| 
 | ||||
|         self.assertIn("(788/1000) @ ", str(item)) | ||||
| 
 | ||||
|     def test_simulation_inside_determination(self): | ||||
|         """Test that running a simulation passes the proper inside value.""" | ||||
|         # get at least one simulation in the DB | ||||
|         original_item, _, _ = PiLog.objects.simulate() | ||||
| 
 | ||||
|         with mock.patch('random.random', return_value=1.0): | ||||
|             miss_item, _, _ = PiLog.objects.simulate() | ||||
|         self.assertEqual(miss_item.total_count_inside, original_item.total_count_inside) | ||||
| 
 | ||||
|         with mock.patch('random.random', return_value=0.0): | ||||
|             hit_item, _, _ = PiLog.objects.simulate() | ||||
|         self.assertGreater(hit_item.total_count_inside, original_item.total_count_inside) | ||||
							
								
								
									
										25
									
								
								tests/test_pi_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/test_pi_views.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| """Test the pi package's views.""" | ||||
| from django.contrib.auth.models import User | ||||
| from rest_framework.status import HTTP_201_CREATED | ||||
| from rest_framework.test import APITestCase | ||||
| 
 | ||||
| from pi.models import PiLog | ||||
| 
 | ||||
| 
 | ||||
| class PiAPITest(APITestCase): | ||||
|     """Test pi DRF views.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         """Do pre-test stuff.""" | ||||
|         self.client = self.client_class() | ||||
|         self.user = User.objects.create(username='test') | ||||
|         self.client.force_authenticate(user=self.user) | ||||
| 
 | ||||
|     def test_simulate_creates_simulation(self): | ||||
|         """Test that the simulate action creates a log entry.""" | ||||
|         self.assertEqual(PiLog.objects.count(), 0) | ||||
| 
 | ||||
|         resp = self.client.post('/pi/api/simulations/simulate/') | ||||
| 
 | ||||
|         self.assertEqual(resp.status_code, HTTP_201_CREATED) | ||||
|         self.assertEqual(PiLog.objects.count(), 2)      # 2 because 0 entry and the real entry | ||||
							
								
								
									
										201
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								tox.ini
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,201 @@ | ||||
| # tox (https://tox.readthedocs.io/) is a tool for running tests | ||||
| # in multiple virtualenvs. This configuration file will run the | ||||
| # test suite on all supported python versions. To use it, "pip install tox" | ||||
| # and then run "tox" from this directory. | ||||
| 
 | ||||
| [tox] | ||||
| envlist = begin,py36,py37,py38,coverage,security,lint,bundle | ||||
| 
 | ||||
| [testenv] | ||||
| # build a wheel and test it | ||||
| wheel = true | ||||
| wheel_build_env = build | ||||
| 
 | ||||
| # whitelist commands we need | ||||
| whitelist_externals = cp | ||||
| 
 | ||||
| # install everything via requirements-dev.txt, so that developer environment | ||||
| # is the same as the tox environment (for ease of use/no weird gotchas in | ||||
| # local dev results vs. tox results) and also to avoid ticky-tacky maintenance | ||||
| # of "oh this particular env has weird results unless I install foo" --- just | ||||
| # shotgun blast install everything everywhere | ||||
| deps = | ||||
|     -rrequirements/requirements-dev.txt | ||||
| 
 | ||||
| [testenv:build] | ||||
| # require setuptools when building | ||||
| deps = setuptools | ||||
| 
 | ||||
| [testenv:begin] | ||||
| # clean up potential previous coverage runs | ||||
| skip_install = true | ||||
| commands = coverage erase | ||||
| 
 | ||||
| [testenv:py36] | ||||
| # run pytest with coverage | ||||
| commands = | ||||
|     pytest --cov-append --cov-branch \ | ||||
|          --cov={envsitepackagesdir}/acro/ \ | ||||
|          --cov={envsitepackagesdir}/countdown/ \ | ||||
|          --cov={envsitepackagesdir}/dice/ \ | ||||
|          --cov={envsitepackagesdir}/dispatch/ \ | ||||
|          --cov={envsitepackagesdir}/dr_botzo/ \ | ||||
|          --cov={envsitepackagesdir}/facts/ \ | ||||
|          --cov={envsitepackagesdir}/gitlab_bot/ \ | ||||
|          --cov={envsitepackagesdir}/ircbot/ \ | ||||
|          --cov={envsitepackagesdir}/karma/ \ | ||||
|          --cov={envsitepackagesdir}/markov/ \ | ||||
|          --cov={envsitepackagesdir}/mpdbot/ \ | ||||
|          --cov={envsitepackagesdir}/pi/ \ | ||||
|          --cov={envsitepackagesdir}/races/ \ | ||||
|          --cov={envsitepackagesdir}/seen/ \ | ||||
|          --cov={envsitepackagesdir}/storycraft/ \ | ||||
|          --cov={envsitepackagesdir}/transform/ \ | ||||
|          --cov={envsitepackagesdir}/twitter/ \ | ||||
|          --cov={envsitepackagesdir}/weather/ | ||||
| 
 | ||||
| [testenv:py37] | ||||
| # run pytest with coverage | ||||
| commands = | ||||
|     pytest --cov-append --cov-branch \ | ||||
|          --cov={envsitepackagesdir}/acro/ \ | ||||
|          --cov={envsitepackagesdir}/countdown/ \ | ||||
|          --cov={envsitepackagesdir}/dice/ \ | ||||
|          --cov={envsitepackagesdir}/dispatch/ \ | ||||
|          --cov={envsitepackagesdir}/dr_botzo/ \ | ||||
|          --cov={envsitepackagesdir}/facts/ \ | ||||
|          --cov={envsitepackagesdir}/gitlab_bot/ \ | ||||
|          --cov={envsitepackagesdir}/ircbot/ \ | ||||
|          --cov={envsitepackagesdir}/karma/ \ | ||||
|          --cov={envsitepackagesdir}/markov/ \ | ||||
|          --cov={envsitepackagesdir}/mpdbot/ \ | ||||
|          --cov={envsitepackagesdir}/pi/ \ | ||||
|          --cov={envsitepackagesdir}/races/ \ | ||||
|          --cov={envsitepackagesdir}/seen/ \ | ||||
|          --cov={envsitepackagesdir}/storycraft/ \ | ||||
|          --cov={envsitepackagesdir}/transform/ \ | ||||
|          --cov={envsitepackagesdir}/twitter/ \ | ||||
|          --cov={envsitepackagesdir}/weather/ | ||||
| 
 | ||||
| [testenv:py38] | ||||
| # run pytest with coverage | ||||
| commands = | ||||
|     pytest --cov-append --cov-branch \ | ||||
|          --cov={envsitepackagesdir}/acro/ \ | ||||
|          --cov={envsitepackagesdir}/countdown/ \ | ||||
|          --cov={envsitepackagesdir}/dice/ \ | ||||
|          --cov={envsitepackagesdir}/dispatch/ \ | ||||
|          --cov={envsitepackagesdir}/dr_botzo/ \ | ||||
|          --cov={envsitepackagesdir}/facts/ \ | ||||
|          --cov={envsitepackagesdir}/gitlab_bot/ \ | ||||
|          --cov={envsitepackagesdir}/ircbot/ \ | ||||
|          --cov={envsitepackagesdir}/karma/ \ | ||||
|          --cov={envsitepackagesdir}/markov/ \ | ||||
|          --cov={envsitepackagesdir}/mpdbot/ \ | ||||
|          --cov={envsitepackagesdir}/pi/ \ | ||||
|          --cov={envsitepackagesdir}/races/ \ | ||||
|          --cov={envsitepackagesdir}/seen/ \ | ||||
|          --cov={envsitepackagesdir}/storycraft/ \ | ||||
|          --cov={envsitepackagesdir}/transform/ \ | ||||
|          --cov={envsitepackagesdir}/twitter/ \ | ||||
|          --cov={envsitepackagesdir}/weather/ | ||||
| 
 | ||||
| [testenv:coverage] | ||||
| # report on coverage runs from above | ||||
| skip_install = true | ||||
| commands = | ||||
|     coverage report --fail-under=95 --show-missing | ||||
| 
 | ||||
| [testenv:security] | ||||
| # run security checks | ||||
| # | ||||
| # again it seems the most valuable here to run against the packaged code | ||||
| commands = | ||||
|     bandit \ | ||||
|          {envsitepackagesdir}/acro/ \ | ||||
|          {envsitepackagesdir}/countdown/ \ | ||||
|          {envsitepackagesdir}/dice/ \ | ||||
|          {envsitepackagesdir}/dispatch/ \ | ||||
|          {envsitepackagesdir}/dr_botzo/ \ | ||||
|          {envsitepackagesdir}/facts/ \ | ||||
|          {envsitepackagesdir}/gitlab_bot/ \ | ||||
|          {envsitepackagesdir}/ircbot/ \ | ||||
|          {envsitepackagesdir}/karma/ \ | ||||
|          {envsitepackagesdir}/markov/ \ | ||||
|          {envsitepackagesdir}/mpdbot/ \ | ||||
|          {envsitepackagesdir}/pi/ \ | ||||
|          {envsitepackagesdir}/races/ \ | ||||
|          {envsitepackagesdir}/seen/ \ | ||||
|          {envsitepackagesdir}/storycraft/ \ | ||||
|          {envsitepackagesdir}/transform/ \ | ||||
|          {envsitepackagesdir}/twitter/ \ | ||||
|          {envsitepackagesdir}/weather/ \ | ||||
|          -r | ||||
| 
 | ||||
| [testenv:lint] | ||||
| # run style checks | ||||
| commands = | ||||
|     flake8 | ||||
|     - flake8 --disable-noqa --ignore= --select=E,W,F,C,D,A,G,B,I,T,M,DUO | ||||
| 
 | ||||
| [testenv:bundle] | ||||
| # take extra actions (build sdist, sphinx, whatever) to completely package the app | ||||
| commands = | ||||
|     cp -r {distdir} . | ||||
|     python setup.py sdist | ||||
| 
 | ||||
| [coverage:paths] | ||||
| source = | ||||
|     ./ | ||||
|     .tox/**/site-packages/ | ||||
| 
 | ||||
| [coverage:run] | ||||
| branch = True | ||||
| 
 | ||||
| # redundant with pytest --cov above, but this tricks the coverage.xml report into | ||||
| # using the full path, otherwise files with the same name in different paths | ||||
| # get clobbered. maybe appends would fix this, IDK | ||||
| include = | ||||
|     {envsitepackagesdir}/acro/ | ||||
|     {envsitepackagesdir}/countdown/ | ||||
|     {envsitepackagesdir}/dice/ | ||||
|     {envsitepackagesdir}/dispatch/ | ||||
|     {envsitepackagesdir}/dr_botzo/ | ||||
|     {envsitepackagesdir}/facts/ | ||||
|     {envsitepackagesdir}/gitlab_bot/ | ||||
|     {envsitepackagesdir}/ircbot/ | ||||
|     {envsitepackagesdir}/karma/ | ||||
|     {envsitepackagesdir}/markov/ | ||||
|     {envsitepackagesdir}/mpdbot/ | ||||
|     {envsitepackagesdir}/pi/ | ||||
|     {envsitepackagesdir}/races/ | ||||
|     {envsitepackagesdir}/seen/ | ||||
|     {envsitepackagesdir}/storycraft/ | ||||
|     {envsitepackagesdir}/transform/ | ||||
|     {envsitepackagesdir}/twitter/ | ||||
|     {envsitepackagesdir}/weather/ | ||||
| 
 | ||||
| omit = | ||||
|     **/_version.py | ||||
| 
 | ||||
| [flake8] | ||||
| enable-extensions = G,M | ||||
| exclude = | ||||
|     .tox/ | ||||
|     versioneer.py | ||||
|     _version.py | ||||
|     instance/ | ||||
| extend-ignore = T101 | ||||
| max-complexity = 10 | ||||
| max-line-length = 120 | ||||
| 
 | ||||
| [isort] | ||||
| line_length = 120 | ||||
| 
 | ||||
| [pytest] | ||||
| python_files = | ||||
|     *_tests.py | ||||
|     tests.py | ||||
|     test_*.py | ||||
| DJANGO_SETTINGS_MODULE = dr_botzo.settings | ||||
| django_find_project = false | ||||
| @ -33,7 +33,7 @@ class Weather(Plugin): | ||||
|         if len(queryitems) <= 0: | ||||
|             return | ||||
| 
 | ||||
|         weather = weather_summary(queryitems[0]) | ||||
|         weather = weather_summary(query) | ||||
|         weather_output = (f"Weather in {weather['location']}: {weather['current']['description']}. " | ||||
|                           f"{weather['current']['temp_F']}/{weather['current']['temp_C']}, " | ||||
|                           f"feels like {weather['current']['feels_like_temp_F']}/" | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| """Get results of weather queries.""" | ||||
| import logging | ||||
| import requests | ||||
| from urllib.parse import quote | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| @ -9,7 +10,7 @@ logger = logging.getLogger(__name__) | ||||
| def query_wttr_in(query): | ||||
|     """Hit the wttr.in JSON API with the provided query.""" | ||||
|     logger.info(f"about to query wttr.in with '{query}'") | ||||
|     response = requests.get(f'http://wttr.in/{query}?format=j1') | ||||
|     response = requests.get(f'http://wttr.in/{quote(query)}?format=j1') | ||||
|     response.raise_for_status() | ||||
| 
 | ||||
|     weather_info = response.json() | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user