Python Full Stack Trainer
Week 1 Study Guide
Python Syntax & Core Data Types
Includes: Line-by-Line Explanations + Full Solutions for Every Exercise
Phase 1 - Weeks 1-2 3 hrs/day - 5 days
How to Use This Guide
This guide is different from typical coding books. Every concept comes with:
Concept Plain English before any code — what it is and why it matters
explanation
Annotated Every code line has a column explaining exactly what it does
code
Exercise What to build — with starter code to help you begin
Complete Full working code with comments — study it after you try
solution
Expected Exactly what you should see in the terminal when correct
output
Study pattern: Read the concept → Try the exercise yourself → Check the solution → Understand
every difference → Move on.
Week 1 Overview
This week you build the complete foundation of Python. Coming from another language, many
concepts will feel familiar — focus on the Python-specific syntax and idioms.
Day Topics Exercises
Monday Variables, print, f-strings, operators Personal info printer, Calculator, Type detective
Tuesday Strings, slicing, type conversion, String transformer, Age calculator, Palindrome
input checker
Wednesday Lists, tuples, for/while loops Marks manager, Shopping list, City coordinates
Thursday Dictionaries, sets Word frequency counter, Set operations
Friday Review, quiz, mini-project Student record system
MONDAY — Day 1 of 5
Monday: Variables, print() and Operators
Goal: Get Python running and understand how to store values, display them, and perform
calculations.
Hour 1 Hour 2 Hour 3
Setup + variables + print() f-strings + operators 3 exercises + GitHub commit
Part 1 — Variables
A variable is a named box that holds a value. Python figures out the type automatically — you just
write the name, =, and the value.
name = "Sourabh" Store text (string) in a variable called name
age = 30 Store a whole number (int) in age
height = 5.9 Store a decimal number (float) in height
is_trainer = True Store True or False (bool) in is_trainer
print(name) Display the value of name: Sourabh
print(age) Display the value of age: 30
print(type(age)) Display the data type: <class 'int'>
print(type(name)) Display the data type: <class 'str'>
f-strings — embedding variables in text
An f-string starts with f before the quote. Anything inside {} is evaluated and inserted.
name = "Sourabh" Store a name
age = 30 Store an age
print(f"Hi, I am {name}.") Output: Hi, I am Sourabh.
print(f"I am {age} years old.") Output: I am 30 years old.
print(f"Next year: {age + 1}") Expressions work inside {}: Output: 31
print(f"Name has {len(name)} characters.") Functions work too: Output: 7
Part 2 — Operators
x = 10 Store 10 in x
y = 3 Store 3 in y
print(x + y) Addition: 13
print(x - y) Subtraction: 7
print(x * y) Multiplication: 30
print(x / y) Division: 3.333... (always float)
print(x // y) Floor division: 3 (drops the decimal)
print(x % y) Modulus (remainder): 1 (10 = 3*3 + 1)
print(x ** y) Exponent: 1000 (10 to the power 3)
print(x > y) Comparison: True (10 is greater than 3)
print(x == y) Equality: False (10 does NOT equal 3)
print(x != y) Not equal: True (they are different)
Monday Exercises
Exercise 1: Personal Info Printer
Task:
Create 4 variables: your name (string), city (string), age (int), and favourite programming language
(string). Print ONE sentence using all four variables in a single f-string.
Starter Code — fill in the blanks:
name = "___" # replace with your name
city = "___" # replace with your city
age = ___ # replace with your age (number, no quotes)
language = "___" # replace with a language name
print(f"My name is {name}...")
Code Explanation (line by line):
Code What it means
name = "Sourabh" Store your name as a string
city = "Ujjain" Store your city
age = 30 Store age as an integer — no quotes!
language = "Python" Store language name
print(f"My name is {name}, I live in Use all 4 variables in one f-string
{city}, I am {age} years old and my
favourite language is {language}.")
Complete Solution:
name = "Sourabh"
city = "Ujjain"
age = 30
language = "Python"
print(f"My name is {name}, I live in {city},")
print(f"I am {age} years old and my favourite language is {language}.")
Expected Output:
My name is Sourabh, I live in Ujjain,
I am 30 years old and my favourite language is Python.
Exercise 2: Simple Calculator
Task:
Store two numbers in variables a and b. Print their sum, difference, product, and the remainder when
a is divided by b. Use f-strings with clear labels for each result.
Starter Code — fill in the blanks:
a = ___ # pick any number
b = ___ # pick any number (not zero!)
print(f"Sum: {___}")
print(f"Difference: {___}")
print(f"Product: {___}")
print(f"Remainder: {___}")
Code Explanation (line by line):
Code What it means
a = 17 Store first number
b = 5 Store second number
print(f"Sum: {a + b}") f-string with expression inside {}. Output: 22
print(f"Difference: {a - b}") Output: 12
print(f"Product: {a * b}") Output: 85
print(f"Remainder: {a % b}") % gives the remainder after division. Output: 2
Complete Solution:
a = 17
b = 5
print(f"Sum: {a + b}")
print(f"Difference: {a - b}")
print(f"Product: {a * b}")
print(f"Remainder: {a % b}")
print(f"Quotient: {a // b}")
Expected Output:
Sum: 22
Difference: 12
Product: 85
Remainder: 2
Quotient: 3
Exercise 3: Type Detective
Task:
Create one variable of each type: int, float, str, bool. For each variable, print the value AND its type()
on the same line using an f-string.
Starter Code — fill in the blanks:
x = ___ # make this an int
y = ___ # make this a float
s = ___ # make this a string
b = ___ # make this a bool
# Print each like: "42 is of type <class 'int'>"
print(f"{x} is of type {type(x)}")
Code Explanation (line by line):
Code What it means
x = 42 Integer — whole number, no decimal, no quotes
y = 3.14 Float — decimal number
s = "Python" String — text always in quotes
b = True Bool — must be True or False, capital first letter
print(f"{x} is of type {type(x)}") type(x) returns <class 'int'> — inserted by {}
print(f"{y} is of type {type(y)}") Output: 3.14 is of type <class 'float'>
print(f"{s} is of type {type(s)}") Output: Python is of type <class 'str'>
print(f"{b} is of type {type(b)}") Output: True is of type <class 'bool'>
Complete Solution:
x = 42
y = 3.14
s = "Python"
b = True
print(f"{x} is of type {type(x)}")
print(f"{y} is of type {type(y)}")
print(f"{s} is of type {type(s)}")
print(f"{b} is of type {type(b)}")
Expected Output:
42 is of type <class 'int'>
3.14 is of type <class 'float'>
Python is of type <class 'str'>
True is of type <class 'bool'>
TUESDAY — Day 2 of 5
Tuesday: Strings, Type Conversion & Input
Goal: Master Python string methods and safely handle user input with type conversion.
Hour 1 Hour 2 Hour 3
String methods and slicing Type conversion + user input 3 exercises + commit
Part 1 — String Methods
Strings are immutable — methods return a NEW string, they never change the original.
s = " Hello, Python World! " Store a string with leading/trailing spaces
[Link]() Remove whitespace from both ends → 'Hello, Python
World!'
[Link]() All lowercase → ' hello, python world! '
[Link]() All uppercase → ' HELLO, PYTHON WORLD! '
[Link]("Python", "Beautiful") Replace first argument with second
[Link](", ") Split on ', ' → returns a list of parts
[Link]().startswith("Hello") Chain methods: strip first, then check start
len(s) Count characters including spaces: 24
"Python" in s Check if substring exists: True
"Java" in s Check if substring exists: False
String slicing — extract parts of a string
s = "Python" 6 characters: P=0, y=1, t=2, h=3, o=4, n=5
s[0] Index 0 = first character: 'P'
s[-1] Index -1 = last character: 'n'
s[0:3] From index 0 up to (not including) 3: 'Pyt'
s[2:] From index 2 to end: 'thon'
s[:4] From start to index 3: 'Pyth'
s[::-1] Step -1 means reversed: 'nohtyP'
Type conversion — critical for user input
input() ALWAYS returns a string. You must convert it before doing maths.
int("42") String to integer: 42
float("3.14") String to float: 3.14
str(100) Integer to string: '100'
bool(0) Zero → False; anything non-zero → True
bool("") Empty string → False; non-empty → True
age = input("Age: ") User types 25 — but age is the STRING '25'
age = int(age) Now age is the INTEGER 25
# SAFE pattern — wrap in try/except
try: Try this block...
n = int(input("Enter number: ")) If user types 'abc' this raises ValueError
print(f"Double: {n*2}") Only runs if conversion succeeded
except ValueError: Catches the error if conversion failed
print("Please enter a number!") Show friendly message instead of crashing
Tuesday Exercises
Exercise 4: String Transformer
Task:
Start with this messy string: ' python is awesome '. Strip whitespace, make each word start with a
capital, replace 'awesome' with 'powerful', then print the final result and its character length.
Starter Code — fill in the blanks:
s = " python is awesome "
# Step 1: strip whitespace
s = s._____()
# Step 2: capitalise each word (title case)
s = s._____()
# Step 3: replace the word
s = s._____("awesome", "powerful")
print(f"Result: {s}")
print(f"Length: {___}")
Code Explanation (line by line):
Code What it means
s = " python is awesome " Original string with leading and trailing spaces
s = [Link]() Remove spaces from both ends: 'python is awesome'
s = [Link]() title() capitalises first letter of each word
s = [Link]("awesome","powerful") Replace the word
print(f"Result: {s}") Print the transformed string
print(f"Length: {len(s)}") len() counts characters in the string
Complete Solution:
s = " python is awesome "
s = [Link]() # remove spaces
s = [Link]() # capitalise each word
s = [Link]("awesome", "powerful") # replace word
print(f"Result: {s}")
print(f"Length: {len(s)}")
Expected Output:
Result: Python Is Powerful
Length: 18
Exercise 5: Input Age Calculator
Task:
Ask the user to enter their birth year. Convert it to an integer. Calculate their age (2025 - birth_year).
Print 'You are X years old.' Wrap in try/except to handle non-numeric input.
Starter Code — fill in the blanks:
# Ask user for input
raw = input("Enter your birth year: ")
try:
year = ___(___) # convert string to int
age = ___ - ___ # subtract from 2025
print(f"You are ___ years old.")
except ___:
print("Please enter a valid year (number).")
Code Explanation (line by line):
Code What it means
raw = input("Enter your birth year: ") input() always returns string, e.g. '1994'
try: Try the code block below
year = int(raw) Convert '1994' string to integer 1994
age = 2025 - year Calculate: 2025 - 1994 = 31
print(f"You are {age} years old.") Display result with f-string
except ValueError: Catches error if raw was e.g. 'abc'
print("Please enter a valid year.") Show helpful message instead of crash
Complete Solution:
raw = input("Enter your birth year: ")
try:
year = int(raw) # convert string to int
age = 2025 - year # calculate age
print(f"You are {age} years old.")
except ValueError:
print("Please enter a valid year (numbers only).")
Expected Output:
Enter your birth year: 1994
You are 31 years old.
--- if user types abc ---
Enter your birth year: abc
Please enter a valid year (numbers only).
Exercise 6: Palindrome Checker
Task:
Ask the user to enter a word. Convert to lowercase and strip spaces. Use string slicing to reverse it.
Check if the original equals the reversed. Print 'Yes, palindrome!' or 'No, not a palindrome.'
Starter Code — fill in the blanks:
word = input("Enter a word: ").lower().strip()
# Hint: reversed_word = word[___] (use slicing with step -1)
reversed_word = ___
if word ___ reversed_word:
print("Yes, palindrome!")
else:
print("No, not a palindrome.")
Code Explanation (line by line):
Code What it means
word = input("Enter a word: Get input, make lowercase, remove spaces — all in one
").lower().strip() line
reversed_word = word[::-1] [::-1] means start to end with step -1 = reversed
if word == reversed_word: == checks if both strings are identical
print("Yes, palindrome!") Runs only if word equals its reverse
else: Otherwise...
print("No, not a palindrome.") Runs if they are different
Complete Solution:
word = input("Enter a word: ").lower().strip()
reversed_word = word[::-1] # step -1 reverses the string
if word == reversed_word:
print(f"Yes, '{word}' is a palindrome!")
else:
print(f"No, '{word}' is not a palindrome.")
print(f"Reversed it is: {reversed_word}")
Expected Output:
Enter a word: racecar
Yes, 'racecar' is a palindrome!
--- another run ---
Enter a word: python
No, 'python' is not a palindrome.
Reversed it is: nohtyp
WEDNESDAY — Day 3 of 5
Wednesday: Lists, Tuples and Loops
Goal: Store, access, modify, and iterate over ordered collections. Lists are the backbone of
almost every Python program.
Hour 1 Hour 2 Hour 3
Lists — all methods Tuples + for/while loops 3 exercises + commit
Part 1 — Lists
Lists are mutable (changeable), ordered, and can hold any data type — even mixed types.
fruits = ["apple", "banana", "mango"] Create a list with 3 strings
# ACCESSING
fruits[0] 'apple' — index starts at 0
fruits[-1] 'mango' — -1 is always the last item
fruits[1:3] ['banana', 'mango'] — index 1 to 2 (3 excluded)
# ADDING
[Link]("grape") Add to the end of the list
[Link](1, "kiwi") Insert at position 1 — shifts others right
# REMOVING
[Link]("banana") Remove by value — raises error if not found
[Link]() Remove and return the LAST item
[Link](0) Remove and return item at index 0
# INFO
len(fruits) Count of items in list
"apple" in fruits True if 'apple' is in the list
[Link]("mango") Returns the index position of 'mango'
# SORTING
[Link]() Sort in-place A to Z — changes the original list
sorted(fruits) Returns new sorted list — original unchanged
Part 2 — Loops
fruits = ["apple", "banana", "mango"] Our list
# Basic for loop
for fruit in fruits: Each iteration, fruit = next item in list
print(fruit) Indented 4 spaces — runs for each item
# enumerate() — get index AND value
for i, fruit in enumerate(fruits): i = index number, fruit = item
print(f"{i}: {fruit}") Output: 0: apple, 1: banana, 2: mango
# range() — generate numbers
for n in range(5): n goes: 0, 1, 2, 3, 4 (stops before 5)
print(n)
for n in range(1, 6): n goes: 1, 2, 3, 4, 5
for n in range(0, 10, 2): n goes: 0, 2, 4, 6, 8 (step of 2)
# break and continue
for fruit in fruits: Loop through list
if fruit == "banana":
continue Skip this iteration — go to next fruit
print(fruit) This only prints apple and mango
Wednesday Exercises
Exercise 7: Student Marks Manager
Task:
You have a list of 5 student marks. Find and print: (1) the highest mark, (2) the lowest mark, (3) the
average rounded to 2 decimal places, and (4) all marks above 70.
Starter Code — fill in the blanks:
marks = [88, 65, 92, 74, 55]
highest = ___(marks)
lowest = ___(marks)
average = ___(marks) / ___(marks)
print(f'Highest: {highest}')
print(f'Lowest: {lowest}')
print(f"Average: {round(___, 2)}")
# Find marks above 70
above_70 = []
for mark in marks:
if mark ___ 70:
above_70._____(mark)
Code Explanation (line by line):
Code What it means
marks = [88, 65, 92, 74, 55] List of 5 integer marks
highest = max(marks) max() returns the largest value in a list
lowest = min(marks) min() returns the smallest value
average = sum(marks) / len(marks) sum() adds all; len() counts — divide for average
print(f"Highest: {highest}") Output: 92
print(f"Lowest: {lowest}") Output: 55
print(f"Average: {round(average, 2)}") round(3.456, 2) → 3.46. Output: 74.8
above_70 = [] Start with empty list
for mark in marks: Loop through each mark
if mark > 70: Condition: only marks greater than 70
above_70.append(mark) Add qualifying mark to our list
print(f"Above 70: {above_70}") Output: [88, 92, 74]
Complete Solution:
marks = [88, 65, 92, 74, 55]
highest = max(marks) # largest value
lowest = min(marks) # smallest value
average = sum(marks) / len(marks) # sum / count
print(f"Highest: {highest}")
print(f"Lowest: {lowest}")
print(f"Average: {round(average, 2)}")
above_70 = []
for mark in marks:
if mark > 70:
above_70.append(mark)
print(f"Above 70: {above_70}")
print(f"Count above 70: {len(above_70)}")
Expected Output:
Highest: 92
Lowest: 55
Average: 74.8
Above 70: [88, 92, 74]
Count above 70: 3
Exercise 8: Shopping List App
Task:
Start with an empty list. Use a loop to ask the user to enter 3 items (using input()). After the loop, print
the full sorted list and the total item count.
Starter Code — fill in the blanks:
shopping = []
for i in range(___): # loop 3 times
item = input(f"Enter item {i+1}: ")
shopping._____(item) # add to list
shopping.___() # sort alphabetically
print(f"Your list: {shopping}")
print(f"Total items: {___}")
Code Explanation (line by line):
Code What it means
shopping = [] Create empty list to fill up
for i in range(3): Loop 3 times: i = 0, 1, 2
item = input(f"Enter item {i+1}: ") Ask user — i+1 shows 1, 2, 3 (not 0, 1, 2)
[Link](item) Add typed item to end of list
[Link]() Sort the list alphabetically in-place
print(f"Your list: {shopping}") Show completed sorted list
print(f"Total items: {len(shopping)}") len() counts items: 3
Complete Solution:
shopping = []
for i in range(3):
item = input(f"Enter item {i+1}: ")
[Link](item)
[Link]() # sort A to Z
print(f"\nYour sorted shopping list:")
for i, item in enumerate(shopping, start=1):
print(f" {i}. {item}")
print(f"Total items: {len(shopping)}")
Expected Output:
Enter item 1: Milk
Enter item 2: Apples
Enter item 3: Bread
Your sorted shopping list:
1. Apples
2. Bread
3. Milk
Total items: 3
THURSDAY — Day 4 of 5
Thursday: Dictionaries and Sets
Goal: Understand key-value storage with dictionaries and unique collections with sets.
Hour 1 Hour 2 Hour 3
Dictionaries — all operations Sets + when to use each Word frequency counter project
Part 1 — Dictionaries
A dictionary stores data as key:value pairs. Think of it as a labelled filing cabinet — you look things
up by label (key), not by position (index).
student = {"name": "Arjun", "age": 22, Create dict with 3 key-value pairs
"marks": 88}
student["name"] Access by key: 'Arjun'
[Link]("phone") get() returns None if key missing (no crash)
[Link]("phone", "N/A") get() with default: returns 'N/A' if missing
student["age"] = 23 Update existing key — age is now 23
student["email"] = "a@[Link]" Add new key — email didn't exist before
del student["marks"] Delete a key permanently
len(student) Number of key-value pairs
"name" in student Check if KEY exists: True
[Link]() All keys: dict_keys(['name','age','email'])
[Link]() All values: dict_values(['Arjun',23,'a@[Link]'])
[Link]() All pairs: dict_items([('name','Arjun'),...])
for key, value in [Link](): Loop over key-value pairs together
print(f"{key} = {value}") Print each pair
Thursday Build Project — Word Frequency Counter
This is today's main project. Read the annotated code carefully, then build it from memory.
sentence = input("Enter a sentence: ") Get a full sentence from user
words = [Link]().strip().split() lowercase, remove edges, split into list of words
freq = {} Start with empty dictionary
for word in words: Loop through each word
if word in freq: Has this word been seen before?
freq[word] += 1 Yes — increment its count by 1
else: No — first time seeing this word
freq[word] = 1 Create the key with count 1
sorted_freq = sorted([Link](), sorted() on dict items (list of tuples)
key=lambda x: x[1], reverse=True) Sort by value (count), highest first
print("\nWord frequencies:") Print heading
for word, count in sorted_freq: Unpack each (word, count) tuple
print(f" {word}: {count}") Print each word with its count
Complete Solution with expected output
sentence = input("Enter a sentence: ")
words = [Link]().strip().split()
freq = {}
for word in words:
if word in freq:
freq[word] += 1
else:
freq[word] = 1
sorted_freq = sorted([Link](), key=lambda x: x[1], reverse=True)
print("\nWord frequencies:")
for word, count in sorted_freq:
print(f" {word}: {count}")
most_common = sorted_freq[0]
print(f"\nMost common: '{most_common[0]}' ({most_common[1]} times)")
Expected Output (input: 'the cat sat on the mat the cat sat')
the: 3
cat: 2
sat: 2
on: 1
mat: 1
Most common: 'the' (3 times)
FRIDAY — Day 5 of 5
Friday: Review, Quiz & Trainer Prep
Goal: Identify gaps, build the mini-project, and complete trainer prep tasks.
Hour 1 Hour 2 Hour 3
Self-quiz + review weak topics Friday mini-project 4 trainer prep tasks
Self Quiz — answers included
Q1. What does type("hello") return?
A) <class 'string'>
B) <class 'str'> [Correct Answer]
C) <class 'text'>
D) str
Q2. Which line causes an error?
A) fruits = ['apple','mango']
B) t = (1,2,3); print(t[0])
C) t = (1,2,3); t[0] = 10 [Correct Answer]
D) s = {1,2,3}; [Link](4)
Q3. What is the output of print(10 // 3)?
A) 3.33
B) 3 [Correct Answer]
C) 1
D) 0.33
Q4. How do you safely access a dict key that might not exist?
A) d[key]
B) [Link](key, default) [Correct Answer]
C) [Link](key)
D) [Link](key)
Q5. What does list(set([1,2,2,3,3,3])) produce?
A) [1,2,2,3,3,3]
B) [1,2,3] (order may vary) [Correct Answer]
C) [3,3,3]
D) Error
Friday Mini-Project — Student Record System
Build this from scratch without looking at notes. You have everything you need from this week.
# Student Record System
students = [
{"name": "Arjun", "age": 21, "marks": 88},
{"name": "Priya", "age": 22, "marks": 94},
{"name": "Rohit", "age": 20, "marks": 76},
]
# Task 1: Print all students with marks
# Task 2: Find and print the topper (highest marks)
# Task 3: Print all names sorted alphabetically
# Task 4: Print the class average (round to 2 decimals)
# Task 5: Print students who scored above average
Complete Solution
students = [
{"name": "Arjun", "age": 21, "marks": 88},
{"name": "Priya", "age": 22, "marks": 94},
{"name": "Rohit", "age": 20, "marks": 76},
]
# Task 1: Print all students
print("=== All Students ===")
for s in students:
print(f" {s['name']}: {s['marks']}")
# Task 2: Find topper
topper = max(students, key=lambda s: s['marks'])
print(f"\nTopper: {topper['name']} with {topper['marks']}")
# Task 3: Sorted names
sorted_names = sorted([s['name'] for s in students])
print(f"\nAlphabetical: {sorted_names}")
# Task 4: Average
avg = sum(s['marks'] for s in students) / len(students)
print(f"\nClass average: {round(avg, 2)}")
# Task 5: Above average
above_avg = [s['name'] for s in students if s['marks'] > avg]
print(f"Above average: {above_avg}")
Expected Output:
=== All Students ===
Arjun: 88
Priya: 94
Rohit: 76
Topper: Priya with 94
Alphabetical: ['Arjun', 'Priya', 'Rohit']
Class average: 86.0
Above average: ['Arjun', 'Priya']
Trainer Prep Tasks
Pen Task 1 — Write the 5-line explanation
In your notebook (handwritten), write a 5-line plain-language explanation of Python data types as if
talking to a complete beginner. No code. No jargon. Just clear sentences.
Phone Task 2 — Record yourself (5 minutes)
Open your phone camera. Explain what a Python dictionary is and why it is useful — as if a student is
in front of you. Watch back. Where did you hesitate? What was unclear? Repeat until clean.
Notebook Task 3 — Create 3 quiz questions
Write 3 new MCQs on this week's topics. Include the correct answer and explain WHY the wrong
options are wrong. This builds your question bank.
GitHub Task 4 — Push to GitHub
Commit all this week's files to python-trainer-journey with clear messages like 'Week 1 Monday:
variables and f-strings'. By Week 24 you have a full portfolio.
Week 1 Summary & Checklist
Topic I can...
[ ] Variables, print, f-strings Create and print any variable type
[ ] Data types: int, float, str, bool Identify and convert between
types
[ ] String methods: strip, split, replace Manipulate any string
[ ] Type conversion + user input Safely convert input() to int/float
[ ] String slicing [start:stop:step] Reverse a string, extract
substrings
[ ] List CRUD: append, insert, remove, pop Build and modify a list
[ ] List search/sort: in, index, sort Find and sort list items
[ ] Tuples and unpacking Use tuples for fixed data
[ ] for / while / range / enumerate Loop through any collection
[ ] Dictionary CRUD Store and retrieve key-value data
[ ] Loop over dict with .items() Print all key-value pairs
[ ] Sets: add, remove, union Deduplicate a list with set()
[ ] All exercises completed with solutions reviewed Built all 8 exercises
[ ] Mini-project built Student record system works
[ ] 4 trainer tasks done 5-line explanation, recording,
questions, GitHub