pymulakat 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ include src/pymulakat/*.pyx
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: pymulakat
3
+ Version: 0.1.0
4
+ Summary: Terminal tabanlı güvenli Python mülakat hazırlık aracı
5
+ Author-email: Kaleminkuheylani <eposta@adresiniz.com>
6
+ Requires-Python: >=3.8
7
+ Requires-Dist: click
8
+ Requires-Dist: rich
9
+ Requires-Dist: requests
10
+ Requires-Dist: python-dotenv
11
+ Requires-Dist: pyjwt
@@ -0,0 +1,15 @@
1
+ # PyMulakat KİMDİR?
2
+
3
+ Teknoloji ve yapay zeka çağı her köşeye sızıyor — bunu fark edenlerin sayısı ise hâlâ bir avuç.
4
+
5
+ Python deyince akla veri bilimi geliyor, veri bilimi deyince akla sermaye geliyor. Oysa bir terminal penceresi, bir API anahtarı ve öğrenme iradesi; en güçlü altyapıya sahip şirketlerle aynı oyunu oynamana yetiyor. pythonmulakat.com tam da bu gerçekten doğdu: düşük sermayeyle Python web ortamı kurmanın, VSCode ekranında bir vizyonun vücut bulmasının hikâyesi.
6
+
7
+
8
+ Türkçe, uzun yıllar teknik düşüncenin dışında bırakıldı. AlgoritmaLAR Türkçe yazılmıyor,değişekn isimleri utf-8 olmasına karşın noktalı harflere iki yüzlü yaklaşıyor. . Bu bir kader değil, bir alışkanlıktı — ve alışkanlıklar değişebilir öyle değil mi?
9
+
10
+ Bir dilin gücü, onunla düşünebildiğin derinlikte saklıdır. Yapay zeka çağını Türkçeyle yakalamak; sadece çeviri yapmak değil, üretmek, sorgulamak, inşa etmek demek. pythonmulakat.com bu inancın somut adımı: Türkçe düşünen, Python yazan, VSCode'da sabaha kadar hata ayıklayan herkes için.
11
+
12
+ Çağı yakalamak için erken doğmak gerekmez. Sadece başlamak gerekir.
13
+
14
+
15
+
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "Cython>=3.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pymulakat"
7
+ version = "0.1.0"
8
+ description = "Terminal tabanlı güvenli Python mülakat hazırlık aracı"
9
+
10
+ requires-python = ">=3.8"
11
+ authors = [
12
+ { name = "Kaleminkuheylani", email = "eposta@adresiniz.com" }
13
+ ]
14
+ dependencies = [
15
+ "click",
16
+ "rich",
17
+ "requests",
18
+ "python-dotenv",
19
+ "pyjwt"
20
+ ]
21
+
22
+ [project.scripts]
23
+ pymulakat = "pymulakat.main:main"
24
+
25
+ [tool.setuptools]
26
+ package-dir = {"" = "src"}
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ from setuptools import setup, Extension, find_packages
2
+ from Cython.Build import cythonize
3
+
4
+ extensions = [
5
+ Extension(
6
+ name="pymulakat.main",
7
+ sources=["src/pymulakat/main.pyx"],
8
+ )
9
+ ]
10
+
11
+ setup(
12
+ package_dir={"": "src"},
13
+ packages=find_packages(where="src"),
14
+ ext_modules=cythonize(extensions, language_level="3"),
15
+ include_package_data=True
16
+ )
@@ -0,0 +1 @@
1
+ """pymulakat.commands — CLI komut modülleri"""
@@ -0,0 +1,124 @@
1
+ """
2
+ 🔐 Auth komutları: login, logout
3
+ """
4
+ import http.server
5
+ import socketserver
6
+ import urllib.parse
7
+ import secrets
8
+ import time
9
+ from threading import Thread
10
+
11
+ import click
12
+ from rich.panel import Panel
13
+ from rich.progress import Progress, SpinnerColumn, TextColumn
14
+
15
+ from ..core.ui import console, show_header, show_success, show_error
16
+ from ..core.fs import get_token, save_token, TOKEN_FILE
17
+ from ..core.api import api_request
18
+
19
+
20
+ def register(cli):
21
+
22
+ @cli.command()
23
+ @click.option("-e", "--email", prompt="E-posta", help="Kullanıcı e-posta adresi")
24
+ @click.option("-p", "--password", prompt="Şifre", hide_input=True, help="Kullanıcı şifresi")
25
+ def login(email: str, password: str):
26
+ """🔐 CLI üzerinden giriş yap (OAuth flow)"""
27
+ show_header("🔐 Giriş", "Hesabınıza bağlanılıyor...")
28
+
29
+ token_received = None
30
+
31
+ with Progress(SpinnerColumn(), TextColumn("[primary]Doğrulanıyor...[/]"), console=console) as progress:
32
+ progress.add_task("", total=None)
33
+ try:
34
+ data = api_request("POST", "/auth/cli/init", json={"email": email, "password": password})
35
+ session_id = data.get("session_id")
36
+ if not session_id:
37
+ show_error("session_id alınamadı")
38
+ return
39
+
40
+ state = secrets.token_urlsafe(16)
41
+ port = 3000 + secrets.randbelow(1000)
42
+ redirect_uri = f"http://localhost:{port}/callback"
43
+ auth_url = (
44
+ f"http://www.pythonmulakat.com/authorize"
45
+ f"?session={urllib.parse.quote(session_id)}"
46
+ f"&redirect_uri={urllib.parse.quote(redirect_uri)}"
47
+ f"&state={state}"
48
+ )
49
+
50
+ console.print(Panel(
51
+ f"[info]Lütfen aşağıdaki linki tarayıcınızda açın:[/]\n\n"
52
+ f"[link={auth_url}]{auth_url}[/]\n\n"
53
+ f"[secondary]Giriş yapıp onay verdikten sonra token otomatik alınacaktır.[/]",
54
+ title="🌐 Tarayıcı Onayı", border_style="cyan"
55
+ ))
56
+
57
+ class CallbackHandler(http.server.BaseHTTPRequestHandler):
58
+ def do_GET(self):
59
+ nonlocal token_received
60
+ parsed = urllib.parse.urlparse(self.path)
61
+ if parsed.path == "/callback":
62
+ params = urllib.parse.parse_qs(parsed.query)
63
+ returned_token = params.get("token", [None])[0]
64
+ returned_state = params.get("state", [None])[0]
65
+ if returned_token and returned_state == state:
66
+ token_received = returned_token
67
+ self.send_response(200)
68
+ self.send_header("Content-type", "text/html")
69
+ self.end_headers()
70
+ self.wfile.write(b"Login successful!")
71
+ else:
72
+ self.send_response(400)
73
+ self.end_headers()
74
+ self.wfile.write("Onay alınamadı.".encode("utf-8"))
75
+ else:
76
+ self.send_response(404)
77
+ self.end_headers()
78
+
79
+ def log_message(self, format, *args):
80
+ pass
81
+
82
+ def run_server():
83
+ with socketserver.TCPServer(("localhost", port), CallbackHandler) as httpd:
84
+ httpd.handle_request()
85
+
86
+ Thread(target=run_server, daemon=True).start()
87
+
88
+ try:
89
+ import webbrowser
90
+ webbrowser.open(auth_url)
91
+ except Exception:
92
+ pass
93
+
94
+ except Exception as e:
95
+ show_error(str(e))
96
+ return
97
+
98
+ with Progress(SpinnerColumn(), TextColumn("[primary]Onay bekleniyor...[/]"), console=console) as progress:
99
+ progress.add_task("", total=None)
100
+ for _ in range(60):
101
+ if token_received:
102
+ break
103
+ time.sleep(1)
104
+
105
+ if token_received:
106
+ try:
107
+ save_token(token_received)
108
+ show_success("Giriş başarılı! Token kaydedildi.", "✓ Oturum Açıldı")
109
+ except (PermissionError, OSError) as e:
110
+ show_error(str(e), "Token Kaydedilemedi")
111
+ else:
112
+ show_error("Onay süresi doldu. Lütfen tekrar deneyin.")
113
+
114
+ @cli.command()
115
+ def logout():
116
+ """🚪 Çıkış yap ve token'ı sil"""
117
+ if TOKEN_FILE.exists():
118
+ try:
119
+ TOKEN_FILE.unlink()
120
+ show_success("Çıkış yapıldı. Token silindi.", "✓ Oturum Kapatıldı")
121
+ except PermissionError as e:
122
+ show_error(f"Token silinemedi: {e}")
123
+ else:
124
+ console.print("[warning]⚠ Zaten giriş yapılmamış.[/]")
@@ -0,0 +1,75 @@
1
+ """
2
+ ⚙️ Config komutu — dosya izinlerini yapılandır
3
+ """
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import click
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+ from rich.box import ROUNDED
11
+ from rich.prompt import Prompt
12
+
13
+ from ..core.ui import console, show_header, show_success
14
+ from ..core.config import PERMISSION_PRESETS, TOKEN_FILE, CONFIG_FILE, META_FILE, PYMULAKAT_DIR
15
+ from ..core.fs import load_permission_config, save_permission_config, _set_mode
16
+
17
+
18
+ def register(cli):
19
+
20
+ @cli.command("config")
21
+ @click.option("--preset", type=click.Choice(["private", "standard", "shared"]),
22
+ default=None, help="Hazır izin şablonu seç")
23
+ @click.option("--show", is_flag=True, help="Mevcut ayarları göster")
24
+ def config_cmd(preset: Optional[str], show: bool):
25
+ """⚙️ Dosya izinlerini yapılandır (kullanıcı tarafından)"""
26
+ show_header("⚙️ İzin Yapılandırması", "Dosya erişim izinlerini özelleştirin")
27
+
28
+ perms = load_permission_config()
29
+
30
+ if show:
31
+ descriptions = {
32
+ "token_file": "JWT token (~/.pymulakat/token)",
33
+ "config_file": "Bu config dosyası",
34
+ "solution_file": "İndirilen çözüm dosyaları",
35
+ "test_file": "Gizli test dosyaları",
36
+ "meta_file": "Oturum meta verisi",
37
+ "pymulakat_dir": "~/.pymulakat dizini",
38
+ }
39
+ table = Table(box=ROUNDED, title="Mevcut İzin Ayarları", header_style="bold blue")
40
+ table.add_column("Dosya Türü", style="cyan")
41
+ table.add_column("Octal İzin", style="yellow")
42
+ table.add_column("Açıklama", style="dim")
43
+ for key, val in perms.items():
44
+ table.add_row(key, oct(val), descriptions.get(key, ""))
45
+ console.print(table)
46
+ return
47
+
48
+ if preset:
49
+ perms.update(PERMISSION_PRESETS[preset])
50
+ console.print(f"[success]✓[/] '{preset}' şablonu uygulandı.")
51
+ else:
52
+ console.print("[info]Her dosya türü için izin girin (örn: 0o644) veya boş bırakıp varsayılanı kullanın.[/]\n")
53
+ for key, current in perms.items():
54
+ raw = Prompt.ask(
55
+ f" [cyan]{key}[/]",
56
+ default=oct(current),
57
+ show_default=True,
58
+ ).strip()
59
+ try:
60
+ perms[key] = int(raw, 8)
61
+ except ValueError:
62
+ console.print(f" [warning]Geçersiz değer, {oct(current)} korundu.[/]")
63
+
64
+ save_permission_config(perms)
65
+ show_success(f"İzin ayarları kaydedildi → {CONFIG_FILE}", "✓ Kaydedildi")
66
+
67
+ # Var olan dosyalara hemen uygula
68
+ for path, key in [
69
+ (TOKEN_FILE, "token_file"),
70
+ (CONFIG_FILE, "config_file"),
71
+ (META_FILE, "meta_file"),
72
+ (PYMULAKAT_DIR, "pymulakat_dir"),
73
+ ]:
74
+ if Path(path).exists():
75
+ _set_mode(Path(path), perms[key])
@@ -0,0 +1,164 @@
1
+ """
2
+ 📥 Download komutu — soru listesinden seçim yap ve indir
3
+ """
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.box import ROUNDED
12
+ from rich.progress import Progress, SpinnerColumn, TextColumn
13
+ from rich.prompt import Prompt, Confirm
14
+
15
+ from ..core.ui import console, show_header, show_error
16
+ from ..core.config import FREE_TIER_LIMIT
17
+ from ..core.fs import (
18
+ get_token, load_permission_config, _atomic_write, _set_mode,
19
+ save_interview_meta, get_next_solution_filename
20
+ )
21
+ from ..core.api import api_request, extract_question_number, decode_base64_content
22
+
23
+
24
+ def register(cli):
25
+
26
+ @cli.command("download")
27
+ @click.option("-l", "--lib", default=None, help="Kütüphane filtresi")
28
+ def download_interactive(lib: Optional[str]):
29
+ """📥 Soru listesinden seçim yap ve indir (İlk 20 ücretsiz)"""
30
+ show_header("📥 Soru Seçimi", "İlk 20 soru ücretsiz • Sadece ID gösteriliyor")
31
+
32
+ token = get_token()
33
+ params = {"lib": lib, "limit": FREE_TIER_LIMIT} if lib else {"limit": FREE_TIER_LIMIT}
34
+
35
+ if token and Confirm.ask("🔓 Premium soruları da listelemek ister misiniz?", default=False):
36
+ params = {"lib": lib} if lib else {}
37
+
38
+ try:
39
+ data = api_request("GET", "/interviews/", params=params)
40
+ except SystemExit:
41
+ if not token:
42
+ try:
43
+ data = api_request("GET", "/interviews/", params={**params, "guest": "true"})
44
+ except Exception:
45
+ show_error("Bağlantı hatası. İnternetinizi kontrol edin.")
46
+ return
47
+ else:
48
+ return
49
+
50
+ if not data:
51
+ show_error("Bu kriterlerde soru bulunamadı.")
52
+ return
53
+
54
+ # ── Tablo ────────────────────────────────────────────
55
+ console.print(f"[info]📋 Mevcut Sorular (Ücretsiz: 1-{min(len(data), FREE_TIER_LIMIT)}):[/]")
56
+ table = Table(box=ROUNDED, show_header=True, header_style="bold blue", show_lines=True)
57
+ table.add_column("ID", style="cyan", width=10)
58
+ table.add_column("Konu", style="white", width=35)
59
+ table.add_column("Kütüphane", style="magenta", width=12)
60
+ table.add_column("Zorluk", style="yellow", width=12)
61
+
62
+ for item in data:
63
+ q_num = extract_question_number(item["id"])
64
+ id_display = f"#{item['id']}" + ("[yellow]🔒[/]" if q_num > FREE_TIER_LIMIT else "")
65
+ difficulty_badge = {
66
+ "Beginner": "[green]🟢 Beginner[/]",
67
+ "Junior-Mid": "[yellow]🟡 Junior-Mid[/]",
68
+ "Mid-Senior": "[red]🔴 Mid-Senior[/]",
69
+ }.get(item.get("difficulty", ""), "⚪ Belirtilmemiş")
70
+ topic = item.get("topic", "Başlıksız")
71
+ table.add_row(
72
+ id_display,
73
+ topic[:32] + ("..." if len(topic) > 32 else ""),
74
+ item.get("lib", "unknown"),
75
+ difficulty_badge,
76
+ )
77
+ console.print(table)
78
+
79
+ # ── Seçim ────────────────────────────────────────────
80
+ qid = Prompt.ask("\n📌 İndirmek istediğiniz soru ID'si (örn: pandas_001)", default="")
81
+ if not qid:
82
+ console.print("[warning]⚠ Seçim yapılmadı.[/]")
83
+ return
84
+
85
+ selected = next((item for item in data if item["id"] == qid), None)
86
+ if not selected:
87
+ show_error(f"Soru ID bulunamadı: {qid}")
88
+ return
89
+
90
+ q_num = extract_question_number(qid)
91
+ if q_num > FREE_TIER_LIMIT and not token:
92
+ console.print(Panel(
93
+ f"[error]❌ Soru #{qid} Premium içeriktir.[/]\n"
94
+ f"[info]👉 Erişmek için:[/] [cyan]pymulakat login[/]",
95
+ title="🔒 Premium Gerekiyor", border_style="yellow"
96
+ ))
97
+ return
98
+
99
+ # ── İndirme ──────────────────────────────────────────
100
+ q_lib = selected["lib"]
101
+ output_file = get_next_solution_filename()
102
+
103
+ console.print(f"\n[info]⬇️ İndiriliyor: {q_lib.upper()} / #{qid}[/]")
104
+ with Progress(SpinnerColumn(), TextColumn("[primary]Kodlar hazırlanıyor...[/]"), console=console) as p:
105
+ p.add_task("", total=None)
106
+ try:
107
+ resp = api_request("GET", f"/interviews/download/{q_lib}/{qid}")
108
+ except SystemExit:
109
+ if not token and q_num <= FREE_TIER_LIMIT:
110
+ try:
111
+ resp = api_request("GET", f"/interviews/download/{q_lib}/{qid}", params={"guest": "true"})
112
+ except Exception:
113
+ show_error("İndirme başarısız. Lütfen tekrar deneyin.")
114
+ return
115
+ else:
116
+ return
117
+
118
+ perms = load_permission_config()
119
+
120
+ if resp.get("starter_encoded"):
121
+ starter = decode_base64_content(resp["starter_encoded"])
122
+ try:
123
+ _atomic_write(Path(output_file), starter)
124
+ _set_mode(Path(output_file), perms["solution_file"])
125
+ console.print(f"[success]✓[/] Çözüm dosyası: [cyan]{Path(output_file).resolve()}[/]")
126
+ except (PermissionError, OSError) as e:
127
+ show_error(str(e), "Dosya Yazılamadı")
128
+ return
129
+
130
+ if resp.get("test_encoded"):
131
+ tests = decode_base64_content(resp["test_encoded"])
132
+ test_path = Path(f".test_{qid}.py")
133
+ try:
134
+ _atomic_write(test_path, tests)
135
+ _set_mode(test_path, perms["test_file"])
136
+ except (PermissionError, OSError) as e:
137
+ console.print(f"[warning]⚠ Test dosyası kaydedilemedi: {e}[/]")
138
+
139
+ try:
140
+ save_interview_meta(qid, q_lib, selected.get("topic", ""), output_file)
141
+ except (PermissionError, OSError) as e:
142
+ console.print(f"[warning]⚠ Meta kaydedilemedi: {e}[/]")
143
+
144
+ try:
145
+ subprocess.Popen(["code", output_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
146
+ console.print(f"[success]✓[/] VSCode açılıyor: [cyan]{output_file}[/]")
147
+ except FileNotFoundError:
148
+ pass
149
+
150
+ if not token and q_num <= FREE_TIER_LIMIT:
151
+ console.print(Panel(
152
+ f"[info]💡 Guest moddasınız. İlerlemenizi kaydetmek için:[/]\n"
153
+ f"[cyan]pymulakat login[/]\n"
154
+ f"[secondary]Sonra: pymulakat submit {qid}[/]",
155
+ title="📌 Hatırlatma", border_style="cyan"
156
+ ))
157
+ else:
158
+ console.print(Panel(
159
+ "[info]📌 Sonraki Adımlar:[/]\n"
160
+ f" 1. [cyan]{output_file}[/] dosyasını düzenle\n"
161
+ f" 2. Lokal test: [cyan]pymulakat test {qid}[/]\n"
162
+ f" 3. Sunucuya gönder: [cyan]pymulakat submit {qid}[/]",
163
+ title="🚀 Devam Et", border_style="green"
164
+ ))
@@ -0,0 +1,72 @@
1
+ """
2
+ 📋 Soru ve ilerleme komutları: get, progress
3
+ """
4
+ import json
5
+ from typing import Optional
6
+
7
+ import click
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+ from rich.box import ROUNDED
11
+ from rich.markdown import Markdown
12
+
13
+ from ..core.ui import console, show_header
14
+ from ..core.auth import require_login
15
+ from ..core.api import api_request, _format_duration
16
+
17
+
18
+ def register(cli):
19
+
20
+ @cli.command("get")
21
+ @click.argument("question_id")
22
+ @click.option("-l", "--lib", required=True, help="Kütüphane adı (zorunlu)")
23
+
24
+ def get_question(question_id: str, lib: str):
25
+ """🔍 Belirli bir sorunun detaylarını görüntüle"""
26
+ show_header(f"📝 Soru #{question_id}", f"Kategori: {lib}")
27
+ data = api_request("GET", f"/interviews/{lib}/{question_id}")
28
+ console.print(Panel(
29
+ Markdown(data.get("question", data.get("description", "Açıklama yok"))),
30
+ title="🎯 Görev", border_style="blue"
31
+ ))
32
+ meta_table = Table(show_header=False, box=None, padding=(0, 1))
33
+ meta_table.add_row("[accent]Zorluk:[/]", data.get("difficulty", "Belirtilmemiş"))
34
+ meta_table.add_row("[accent]Konu:[/]", data.get("topic", "Belirtilmemiş"))
35
+ console.print(meta_table)
36
+ if data.get("hints"):
37
+ console.print("\n[info]💡 İpuçları:[/]")
38
+ for i, hint in enumerate(data["hints"], 1):
39
+ console.print(f" [secondary]{i}.[/] {hint}")
40
+
41
+ @cli.command("progress")
42
+ @click.option("-l", "--lib", required=True, help="Kütüphane adı")
43
+ @click.option("-j", "--json", "as_json", is_flag=True, help="JSON formatında çıktı")
44
+
45
+ def show_progress(lib: str, as_json: bool):
46
+ """📈 Kullanıcı ilerleme durumunu görüntüle"""
47
+ show_header("📈 İlerleme Raporu", f"Kategori: {lib.upper()}")
48
+ data = api_request("GET", "/auth/my-progress", params={"lib": lib})
49
+
50
+ if as_json:
51
+ console.print_json(json.dumps(data, indent=2, ensure_ascii=False))
52
+ return
53
+
54
+ summary = Table(box=ROUNDED, show_header=False)
55
+ summary.add_row("[accent]📦 Toplam Soru:[/]", f"[bold]{data.get('total', 0)}[/]")
56
+ completed = data.get("completed", [])
57
+ summary.add_row("[success]✅ Çözülen:[/]", f"[bold green]{len(completed)}[/]")
58
+ if completed:
59
+ accuracy = len(completed) / data.get("total", 1) * 100
60
+ summary.add_row("[info]🎯 Başarı Oranı:[/]", f"[bold cyan]{accuracy:.1f}%[/]")
61
+ console.print(summary)
62
+
63
+ if completed:
64
+ console.print("\n[info]🔹 Tamamlanan Sorular:[/]")
65
+ for item in completed:
66
+ attempt = item.get("attempt", {})
67
+ time_str = _format_duration(attempt.get("time_taken", 0))
68
+ points = attempt.get("points_gained", 0)
69
+ console.print(
70
+ f" [success]✓[/] [cyan]#{item['id']}[/] {item.get('topic')} "
71
+ f"[secondary]→[/] ⏱️ {time_str} | 🏆 +{points} puan"
72
+ )