Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ntijoh-daniel-berg/e89ae1bc03476c39532356eafefed13d to your computer and use it in GitHub Desktop.
Save ntijoh-daniel-berg/e89ae1bc03476c39532356eafefed13d to your computer and use it in GitHub Desktop.

Slutprojekt Programmering 2

Bedömningsmatris

##Planering ##

Aspekt E C A
Design Med viss säkerhet analyserar du uppgiften och designar ditt program Som för E, men med säkerhet, och du motiverar dessutom utförligt dina val
Klassdiagram Du planerar uppgiften med enkla klassdiagram

Kodning och dokumentering

Aspekt E C A
Strukturering Din kod är delvis strukturerad Din kod är i stor utsträckning strukturerad
Kodningsstil Du använder en konsekvent kodningsstil för klasser, moduler, metoder, variabler, kommentarer och dokumentation
Namngivning Du använder en tydlig namngivning i din kod
Dokumentering Din kod är delvis dokumenterad Du dokumenterar utförligt dina klasser och metoder

Felsökning

Aspekt E C A
Typsäkerhet Du uppmärksammar problem med typsäkerhet
Felsökning Du felsöker enkla syntaxfel Som för E, men effektivt, och dessutom även körtidsfel och programmeringslogiska fel Som för C, men med effektivitet (exempelvis med hjälp av tester)

Slutrestultat och Uppföljning

Aspekt E C A
Komplexitet Det färdiga programmet är av enkel karaktär, fungerar tillfredsställande, och är stabilt och robust Som för E, men av lite mer avancerad karaktär Som för C, men programmet är av komplex karaktär, följer SOLID, och fungerar väl
Utvärdering Du utvärderar, med enkla omdömen, programmets prestanda och funktionalitet Som för E, men nyanserade omdömen Som för C, men du ger även förslag på förbättringar

Uppgiftsbeskrivning

Slutuppgiften går ut på att skriva en Reddit-bot, det vill säga ett program, som, med hjälp av Reddits api, kopplar upp sig mot Reddit.

Exempelbotar:

  • Gandhi_spell_bot
  • En bot som, om någon skriver förolämpningar som "du suger!", ger en komplimang till den som blivit förolämpad.
  • En bot som, varje dag, postar väderprognosen för en stad till stadens subbreddit.

Inspiration

Reddits API

Reddit uppmuntrar utvecklare att programmera mot deras API (Application Programming Interface (det vill säga de http-endpoints (endpoint är ett fancy-pancy ord för url) boten kommer prata med)). De flesta större siter och applikationer har API:er för att interagera med andra program.

Viktigt! Begränsningar

Reddit har regler som begränsar vad en bot får göra. Läs dessa och bryt inte mot dem.

Några extra viktiga regler:

  1. Boten får inte göra fler än 60 anrop per minute (se Request Throttler längre ner).

    OBS! Om du bryter mot denna regel kan din bot bli bannad.

    DUBBELOBS! Om du verkligen bryter mot denna regel kan i värsta fall alla skolans/academedias IP-adresser bli blockade från Reddit.

  2. Du måste registrera ett konto för botten (och godkänna termerna).

  3. Boten måste autentisera sig (logga in) med OAUTH 2 (se längre ner).

  4. Boten får inte låtsas vara en annan bot, webbläsare, eller dylikt

  5. Boten måste ha ett unikt namn

  6. Om botens namn innehåller "reddit" måste det stå "for" före reddit (ex: GrillBot for Reddit)

  7. Om du tänker ta betalt för din bot måste du ha godkännande från reddit

Request Throttler

Den första klass till din bot du måste skapa är en "Request Throttler"

Request Throttlerns uppgift är att se förhindra att din applikation skickar för många requests till reddits api, och därmed bannar din bot (eller i värsta fall hela skolan/academedia).

Token Bucket

Jag föreslår starkt att din Request Throttler implementerar Token Bucket Pattern.

I korthet kan man beskriva Token Bucket Pattern så här:

  • Med "token" avses "polett", det vill säga något man använder istället för pengar i t.ex nöjesparker eller spelhallar.
  • Det finns en "hink" som rymmer x (i reddits fall 60) st "tokens".
  • Om det gått x (i reddits fall 60) sekunder sedan hinken senast fyllts på fylls hinken på till max antal (i reddits fall 60) tokens.
  • Varje gång man skickar en request minskas antalet tokens i "hinken" med en.
  • Om hinken är tom får man inte skicka någon request, och måste vänta med nästa request tills hinken fylls på.

Boten kan alltså (teoretiskt sätt) bränna av alla 60 tillgängliga requests på en sekund (men måste sen vänta 59 sekunder innan den kan skicka nästa request).

Din Request Throttler måste vara godkänd av Daniel eller David innan din bot får prata med Reddit

Requests (Python-bibliotek)

För att smidigt prata HTTP med Python rekommenderar jag start Requests: HTTP for Humans.

Installation

python -m pip install requests

Exempel

Båda exempel kräver att boten redan är autentiserad med OAUTH, och har tillgång till sin autentiseringstoken (se OAUTH 2 nedan).

1. Hämta information om boten

Den första endpoint vi hittar i reddits api-lista är get /api/v1/me.

I dokumentationen står det "Returns the identity of the user currently authenticated via OAuth." Vi testar:

result = requests.get("https://oauth.reddit.com/api/v1/me", headers=headers)
print(result.json())
>>> {u'comment_karma': 0,
	 u'created': 1389649907.0,
	 u'created_utc': 1389649907.0,
	 u'has_mail': False,
	 u'has_mod_mail': False,
	 u'has_verified_email': None,
	 u'id': u'1',
	 u'is_gold': False,
	 u'is_mod': True,
	 u'link_karma': 1,
	 u'name': u'reddit_bot',
	 u'over_18': True}

Det som skrivs ut är en Dict med svaret från servern.

2. Hämta inlägg i en subreddit

Förutsatt att boten är inloggad enligt nedan, för att hämta inlägg i /r/programming, sorterade efter "hot"

listing = requests.get("https://reddit.com/r/programming/hot.json", headers=headers)
data = listing.json()['data']

data är nu en gigantisk dict, där alla inlägg i subbredditen ligger i en lista inuti children-nyckeln:

{u'kind': u'Listing', u'data': 
	{u'modhash': u'', u'children': [
		{u'kind': u't3', u'data': 
			{u'domain': u'arstechnica.com', 
			 u'banned_by': None, 
			 u'media_embed': {}, 
			 u'subreddit': u'programming',
			 u'selftext_html': None,
			 u'selftext': u'',
			 u'likes': None,
			 u'suggested_sort': None, 
			 u'user_reports': [],
			 u'secure_media': None,
			 u'link_flair_text': None,
			 u'id': u'4iiagx',
			 u'from_kind': None,
			 u'gilded': 0,
			 u'archived': False,
			 u'clicked': False,
			 u'report_reasons': None,
			 u'author': u'abcrink',
			 u'media': None,
			 u'name': u't3_4iiagx',
			 u'score': 105,
			 u'approved_by': None,
			 u'over_18': False,
			 u'hidden': False,
			 u'thumbnail': u'',
			 u'subreddit_id': u't5_2fwo',
			 u'edited': False,
			 u'link_flair_css_class': None,
			 u'author_flair_css_class': None,
			 u'downs': 0,
			 u'mod_reports': [],
			 u'secure_media_embed': {},
			 u'saved': False,
			 u'removal_reason': None,
			 u'stickied': False,
			 u'from': None,
			 u'is_self':
			 False, u'from_id': None,
			 u'permalink': u'/r/programming/comments/4iiagx/second_oracle_v_google_trial_could_lead_to_huge/', u'locked': False,
			 u'hide_score': False,
			 u'created': 1462801875.0,
			 u'url': u'http://arstechnica.com/tech-policy/2016/05/round-2-of-oracle-v-google-is-an-unpredictable-trial-over-api-fair-use/',
			 u'author_flair_text': None,
			 u'quarantine': False,
			 u'title': u'Second Oracle v. Google trial could lead to huge headaches for developers', u'created_utc': 1462773075.0,
			 u'ups': 105,
			 u'num_comments': 24,
			 u'visited': False,
			 u'num_reports': None,
			 u'distinguished': None}
			}
		},
		...

Som vi ser ovan har varje inlägg en egen data-nyckel, som innehåller information om inlägget:

for child in data['children']:
	print("Author: {0}".format(child['data']['author']))
	print("URL: {0}".format(child['data']['url']))
	print("Title: {0}".format(child['data']['title']))

Om man vill ha ett inläggs unika id, kan man hitta det i dess ['data']['id']:

wanted_thread = data['children'][0] #första inlägget
id = wanted_thread['data']['id']

Man kan sen använda inläggets unika id för att hämta data om inlägget:

thread = requests.get("https://reddit.com/r/programming/comments/{0}.json".format(id), headers=headers)
thread = thread.json()

thread är nu en gigantisk dict med alla inlägg i tråden:

print(thread.json())
{u'kind': u'Listing',
u'data': 
	{u'modhash': u'', u'children': [
		{u'kind': u't3',
			u'data':
				{u'domain': u'arstechnica.com',
				u'banned_by': None, u'media_embed': {}, u'subreddit': u'programming', u'selftext_html': None,
				...
				}
		}
		,...
	}

Man kan också använda permalinken för att hitta inlägget:

permalink = wanted_thread['data']['permalink']
thread = requests.get("https://reddit.com/{0}.json".format(permalink), headers=headers)
thread = thread.json()
Posta kommentarer till ett inlägg i en tråd

Förutsatt att du hämtat en tråd enligt nedan:

thread = requests.get("https://reddit.com/r/programming/comments/{0}.json".format(id), headers=headers)
thread = thread.json()

Varje inlägg i thread['data']['children'] är en kommentar i tråden.

comment = thread['data']['children'][0] #första kommentaren

Varje kommentar har ett helt unikt 'fullname', vilket vi kommer behöva för att posta en kommentar

fullname = comment['name']

Skapa en dict med den data du vill posta, och skicka till https://oauth.reddit.com/api/comment

post_data = {'api_type': 'json', 'text': 'this is a response', 'thing_id': fullname }
response = requests.post("https://oauth.reddit.com/api/comment", data=post_data, headers=headers)

OAUTH 2 (Inloggning och autentisering)

Skapa utvecklar-konto

För att kunna autentisera din bot måste du först skapa ett utvecklar-konto på reddit: OAuth2 Quick Start Example

Autentisering

import requests
import requests.auth
  1. Skapa ett HTTPBasicAuth-objekt med hjälp av client_id & secret för din applikation https://www.reddit.com/prefs/apps/:
client_auth = requests.auth.HTTPBasicAuth('p-jcoLKBynTLew', 'gko_LXELoV07ZBNUXrvWZfzE3aI')
  1. Skapa en "Params-dict" enligt nedan. För er som har läst webbutveckling med PHP: tänk $_POST`
post_data = {"grant_type": "password", "username": "reddit_bot", "password": "snoo"}
  1. Skapa ett unikt User-Agent-header för din bot:
headers = {"User-Agent": "ChangeMeClient/0.1 by YourUsername"}
  1. Posta dina variabler till "https://www.reddit.com/api/v1/access_token" enligt nedan, och spara svaret i en variabel.
response = requests.post("https://www.reddit.com/api/v1/access_token", auth=client_auth, data=post_data, headers=headers)
  1. response-variabeln ovan är ett objekt av klassen Response. response.text innehåller nu en sträng JSON (se nedan) med bland annat din autentiseringstoken. Genom att anropa .json() får du tillbaks en python-dict med innehållet i text-variabeln:
response.json()
>>> {u'access_token': u'fhTdafZI-0ClEzzYORfBSCR7x3M',
 u'expires_in': 3600,
 u'scope': u'*',
 u'token_type': u'bearer'}`
  1. Skapa en ny header-variabel som innehåller din token och övrig info om boten:
headers = {"Authorization": "bearer fhTdafZI-0ClEzzYORfBSCR7x3M", "User-Agent": "ChangeMeClient/0.1 by YourUsername"}`
  1. Skicka alla kommande requests mot reddits api till https://oauth.reddit.com/api/ och skicka med den nya headern.
requests.get("https://oauth.reddit.com/api/v1/me", headers=headers)`

Dokumenteringsstandard

Det finns massor av olika sätt att skriva Docstrings i Python.

Standard i Pycharm är "reStructuredText" (reST), så det är vad vi kommer göra.

I din metod (eller klass), på första raden efter definitionen, skriv tre stycken enkelfnuttar (') och tryck på enter.

Pycharm kommer förpopulera din dokumentationstträng med namnet på argumenten, och, om metoden returnerar något, ett :return:

Exempel

Observera: exempelmetoden ska inte tas som väldesignad OOP-kod.

def comments_for(self, subreddit, sorted_by='new'):
'''

:param subreddit:
:param sorted_by:
:return:
'''

Du behöver fylla på :param med en beskrivning av vad parametern är för något, :return med en beskrivning av vad som returneras.

Om metoden kastar undantag behöver du lägga till :raises.

Innan alla :param, :return och :raises ska du översiktligt beskriva metodens/klassens syfte, och ge ett (eller flera) exempel på hur metoden används.

En färdigifylld kommentar:

def comments_for(self, subreddit, sorted_by='new'):
'''
Returns comments for the given subreddit sorted by new (default), hot or controversial

>>> comments_for(subreddit='3456eex')
>>> [<GrillBot.Comment object at 0x10c688c50>, <GrillBot.Comment object at 0x10c688c90>]

:param subreddit: the id of the subreddit
:param sorted_by: a string; 'new' (default), 'hot', or 'controversial'.
:return: a list of Comment objects ordered by sorted_by
:raises NoSuchSubReddit: if the subreddit can not be found
'''

SOLID

SOLID är samling av 5 designprinciper man bör följa för att skapa väldesignade, vidareutvecklingsbara, modulära & underhållbara program.

Se Sandi Metz presentation "SOLID Object-Oriented Design" på YouTube

S - Single Responsibility Principle

En klass ska bara ha en "uppgift" eller "ansvar". Om du, när du beskriver vad en klass gör, börjar lägga till många "och", är det troligt att du bryter mot Single Responsibility Principle, och behöver flytta ut uppgifter/ansvar till nya klasser.

O - Open/Closed Principle

Open/Closed Principle säger att dina program ska vara "open for extension" men "closed for modification". Med andra ord: om någon vill ändra på delar av vårt program ska de inte behöva ändra (modifiera) i vår kod, utan skriva ny kod ("extend"). I Objektorienterad programmering löser vi det genom arv.

L - Liskov Substitution Principle

Liskov Substitution Principle säger att alla subklasser av en superklass ska kunna bytas ut mot varandra (eller superklassen), utan att programmet slutar fungera. Detta innebär att alla subklassers konstruktorer bör se likadana ut, och att de implementerar samma (publika) metoder.

I - Interface Segregation Principle

Interface Segregation Principle säger att man bör ha många små "interfaces" snarare än få, stora. Anledningen till detta är att ingen klass ska behöva implementera metoder den inte behöver. I Python finns inga interfaces, så Interface Segregation Principle är inte så relevant för oss.

D - Dependency Inversion/Injection Principle

Dependency Inversion/Injection Principle går hand i hand med de övriga principerna. Rent praktiskt innebär det att du aldrig ska nämna en annan klass vid namn inne i en klass. I stället ska du skicka in klassen (eller objekt av klassen) till konstruktorn/metoden. På så sätt kan du alltid ersätta den inskickade klassen/objektet mot en subklass (förutsatt att du följt Open/Closed Principle och Liskov Substitution Principle).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment