cv-renderer 0.1.0__py3-none-any.whl

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.
File without changes
@@ -0,0 +1,5 @@
1
+ """Allow `python -m cv_renderer` — the invocation external callers rely on."""
2
+
3
+ from cv_renderer.render import main
4
+
5
+ main()
@@ -0,0 +1,98 @@
1
+ meta:
2
+ name: Your Name
3
+ title:
4
+ default: Software Engineer
5
+ ai: AI Engineer
6
+ data: Data Engineer
7
+ location: City, Country
8
+ email: you@example.com
9
+ phone: "+1 555 000 0000"
10
+ links:
11
+ linkedin: linkedin.com/in/yourprofile
12
+ github: github.com/yourusername
13
+ portfolio: yourportfolio.com
14
+
15
+ about:
16
+ default: |
17
+ Brief professional summary describing your background and focus.
18
+ ai: |
19
+ AI-focused variant of your summary.
20
+ data: |
21
+ Data-focused variant of your summary.
22
+
23
+ experience:
24
+ - company: Company Name
25
+ title:
26
+ default: Software Engineer
27
+ ai: AI Engineer
28
+ data: Data Engineer
29
+ location: City
30
+ start: "2024"
31
+ end: present
32
+ tags: [ai, data, backend]
33
+ description: "Short description of the team or product."
34
+ bullets:
35
+ - text: "Accomplishment with measurable impact."
36
+ tags: [ai, backend]
37
+ - text: "Another accomplishment."
38
+ tags: [data]
39
+
40
+ - company: Previous Company
41
+ title:
42
+ default: Intern
43
+ ai: Intern
44
+ data: Intern
45
+ location: City
46
+ start: 2023-06
47
+ end: 2023-09
48
+ tags: [ml]
49
+ bullets:
50
+ - text: "What you did and why it mattered."
51
+ tags: [ml]
52
+
53
+ education:
54
+ - institution: University Name
55
+ degree: "Bachelor's Degree, Computer Science"
56
+ location: Country
57
+ start: 2020
58
+ end: 2024
59
+ tags: [always]
60
+
61
+ skills:
62
+ - category: Backend and API
63
+ tags: [backend, always]
64
+ items: "Python, FastAPI, ..."
65
+ - category: Data Engineering
66
+ tags: [data]
67
+ items: "PostgreSQL, dbt Core, ..."
68
+ - category: AI and LLM Systems
69
+ tags: [ai, llm]
70
+ # items can also be a list of {text, tags} to filter within a category,
71
+ # not just across categories — useful when a category mixes core tools
72
+ # (keep for every profile) with niche ones (only relevant to some jobs).
73
+ items:
74
+ - text: LangChain
75
+ tags: [ai]
76
+ - text: LangGraph
77
+ tags: [ai]
78
+ - category: ML and Evaluation
79
+ tags: [ml, ai]
80
+ items: "PyTorch, scikit-learn, ..."
81
+ - category: Infra and DevOps
82
+ tags: [devops, backend]
83
+ items: "Docker, CI/CD, ..."
84
+
85
+ projects:
86
+ - name: Project Name
87
+ subtitle: Open Source, GitHub
88
+ year: 2024
89
+ tags: [ai, backend]
90
+ bullets:
91
+ - text: "What you built and why it matters."
92
+ tags: [ai, backend]
93
+
94
+ additional:
95
+ languages:
96
+ - English (Native)
97
+ certifications:
98
+ - "Your Certification, Issuer"
@@ -0,0 +1,92 @@
1
+ meta:
2
+ name: Adınız Soyadınız
3
+ title:
4
+ default: Yazılım Mühendisi
5
+ ai: Yapay Zeka Mühendisi
6
+ data: Veri Mühendisi
7
+ location: Şehir, Türkiye
8
+ email: siz@ornek.com
9
+ phone: "+90 5XX XXX XX XX"
10
+ links:
11
+ linkedin: linkedin.com/in/profiliniz
12
+ github: github.com/kullaniciadi
13
+ portfolio: portfolyonuz.com
14
+
15
+ about:
16
+ default: |
17
+ Genel profesyonel özetiniz.
18
+ ai: |
19
+ Yapay zeka odaklı özetiniz.
20
+ data: |
21
+ Veri odaklı özetiniz.
22
+
23
+ experience:
24
+ - company: Şirket Adı
25
+ title:
26
+ default: Yazılım Mühendisi
27
+ ai: Yapay Zeka Mühendisi
28
+ data: Veri Mühendisi
29
+ location: Şehir
30
+ start: "2024"
31
+ end: present
32
+ tags: [ai, data, backend]
33
+ description: "Ekip veya ürün kısa açıklaması."
34
+ bullets:
35
+ - text: "Ölçülebilir etkiyle bir başarı."
36
+ tags: [ai, backend]
37
+ - text: "Başka bir başarı."
38
+ tags: [data]
39
+
40
+ - company: Önceki Şirket
41
+ title:
42
+ default: Stajyer
43
+ ai: Stajyer
44
+ data: Stajyer
45
+ location: Şehir
46
+ start: 2023-06
47
+ end: 2023-09
48
+ tags: [ml]
49
+ bullets:
50
+ - text: "Ne yaptınız ve neden önemliydi."
51
+ tags: [ml]
52
+
53
+ education:
54
+ - institution: Üniversite Adı
55
+ degree: "Lisans, Bilgisayar Mühendisliği"
56
+ location: Türkiye
57
+ start: 2020
58
+ end: 2024
59
+ tags: [always]
60
+
61
+ skills:
62
+ - category: Backend ve API
63
+ tags: [backend, always]
64
+ items: "Python, FastAPI, ..."
65
+ - category: Veri Mühendisliği
66
+ tags: [data]
67
+ items: "PostgreSQL, dbt Core, ..."
68
+ - category: AI ve LLM Sistemleri
69
+ tags: [ai, llm]
70
+ items: "LangChain, LangGraph, ..."
71
+ - category: ML ve Değerlendirme
72
+ tags: [ml, ai]
73
+ items: "PyTorch, scikit-learn, ..."
74
+ - category: Altyapı ve DevOps
75
+ tags: [devops, backend]
76
+ items: "Docker, CI/CD, ..."
77
+
78
+ projects:
79
+ - name: Proje Adı
80
+ subtitle: Açık Kaynak, GitHub
81
+ year: 2024
82
+ tags: [ai, backend]
83
+ bullets:
84
+ - text: "Ne inşa ettiniz ve neden önemli."
85
+ tags: [ai, backend]
86
+
87
+ additional:
88
+ languages:
89
+ - Türkçe (Anadil)
90
+ - İngilizce (İş Düzeyi)
91
+ certifications:
92
+ - "Sertifikanız, Kurum"
@@ -0,0 +1,12 @@
1
+ months: [Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
2
+
3
+ about: About Me
4
+ experience: Professional Experience
5
+ education: Education
6
+ skills: Skills
7
+ projects: Projects
8
+ additional: Additional Information
9
+ languages: Languages
10
+ certifications: Certificates
11
+ present: Present
12
+ to: "to"
@@ -0,0 +1,12 @@
1
+ months: [Oca, Şub, Mar, Nis, May, Haz, Tem, Ağu, Eyl, Eki, Kas, Ara]
2
+
3
+ about: Hakkımda
4
+ experience: Profesyonel Deneyim
5
+ education: Eğitim
6
+ skills: Yetenekler
7
+ projects: Projeler
8
+ additional: Ek Bilgiler
9
+ languages: Diller
10
+ certifications: Sertifikalar
11
+ present: Günümüz
12
+ to: "-"
@@ -0,0 +1,13 @@
1
+ name: AI Engineer
2
+ description: Foregrounds LLM systems, agentic AI, and evaluation work
3
+ variant: ai
4
+ lang: en
5
+ focus_tags: [ai, llm, ml]
6
+ deprioritize_tags: [data]
7
+ max_bullets_per_job: 4
8
+ sections: [experience, skills, education, projects, additional]
9
+ skill_categories:
10
+ - AI and LLM Systems
11
+ - ML and Evaluation
12
+ - Backend and API
13
+ - Infra and DevOps
@@ -0,0 +1,49 @@
1
+ name: Spotify - Backend Engineer
2
+ description: Tailored for a specific job posting — demonstrates every override field a company-specific profile can use
3
+ variant: data
4
+ lang: en
5
+
6
+ # Overrides the headline and summary shown in the base data's `variant` lookup.
7
+ # Write these directly for this application; don't translate from the other
8
+ # language file, and don't invent facts — same rules as any other CV content.
9
+ # The summary follows the title -> skills & accomplishments -> career goals
10
+ # structure in docs/references/about-me-section.md.
11
+ # Never name the target company here (or anywhere else in the CV content) —
12
+ # that belongs in a cover letter, not a resume that might get reused or shared.
13
+ title_override: Backend Engineer
14
+ about_override: |
15
+ Backend Engineer with production experience building FastAPI services on top of governed data layers. Shipped a 109-model dbt warehouse layer, added automated data-correctness tests that catch reporting errors at the source, and supported BI reporting through Apache Superset. Looking to bring that same reliability focus to a data platform team.
16
+
17
+ # Replaces an entry's tag-filtered bullets outright — use when a real
18
+ # accomplishment needs reframing around a different angle than its default
19
+ # bullets take (see filter.py::apply_profile). Keyed by ExperienceEntry.company.
20
+ # An override also forces the entry into the CV even if its own tags would
21
+ # otherwise exclude it entirely — only do this when something in it is
22
+ # genuinely relevant; if nothing is, leave it out and let tag filtering drop it.
23
+ experience_overrides:
24
+ Company Name:
25
+ - "Rewritten bullet #1 for this application, angled at what this employer cares about."
26
+ - "Rewritten bullet #2, still real, still XYZ-formatted, still no invented numbers."
27
+
28
+ # Same idea as experience_overrides, but keyed by Project.name.
29
+ project_overrides:
30
+ Project Name:
31
+ - "Rewritten project bullet emphasizing the angle relevant to this posting."
32
+
33
+ # Reorders rendered projects by name — the most relevant project for this
34
+ # application isn't always the first one in the base data. Same mechanism as
35
+ # skill_categories below, just for projects instead of skill categories.
36
+ project_order: [Project Name]
37
+
38
+ focus_tags: [data, backend]
39
+ deprioritize_tags: [ai, llm, ml]
40
+ max_bullets_per_job: 4
41
+ sections: [experience, education, skills, projects, additional]
42
+
43
+ # Filters and orders which skill categories render. Skill items can also carry
44
+ # their own tags (see base_en.yaml's "AI and LLM Systems" category) so a single
45
+ # category can show a different subset of items per profile.
46
+ skill_categories:
47
+ - Data Engineering
48
+ - Backend and API
49
+ - Infra and DevOps
@@ -0,0 +1,9 @@
1
+ name: General
2
+ description: Full CV, no tag filtering
3
+ variant: default
4
+ lang: en
5
+ focus_tags: []
6
+ deprioritize_tags: []
7
+ max_bullets_per_job: 10
8
+ sections: [experience, education, skills, projects, additional]
9
+ skill_categories: ~
@@ -0,0 +1,7 @@
1
+ ai: LLM systems, agentic workflows, evaluation, RAG
2
+ llm: Model-specific work — prompt engineering, tool use, evals
3
+ ml: Training, distillation, PyTorch, model deployment
4
+ data: dbt, SQL, warehouse, BI dashboards, ETL/ELT
5
+ backend: FastAPI, API design, auth, logging, Redis
6
+ devops: Docker, CI/CD, OTEL, pipeline infrastructure
7
+ always: Items that appear in every profile (education, contact)
cv_renderer/exports.py ADDED
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def export_pdf(html_path: Path) -> Path:
7
+ try:
8
+ from playwright.sync_api import sync_playwright
9
+ except ImportError as exc:
10
+ raise SystemExit(
11
+ "PDF export needs the 'pdf' extra: pip install 'cv-renderer[pdf]' "
12
+ "then run: playwright install chromium"
13
+ ) from exc
14
+
15
+ pdf_path = html_path.with_suffix(".pdf")
16
+
17
+ with sync_playwright() as pw:
18
+ browser = pw.chromium.launch()
19
+ page = browser.new_page()
20
+ page.goto(html_path.as_uri())
21
+ page.emulate_media(media="print")
22
+ page.pdf(
23
+ path=str(pdf_path),
24
+ format="A4",
25
+ print_background=True,
26
+ )
27
+ browser.close()
28
+
29
+ return pdf_path
cv_renderer/filter.py ADDED
@@ -0,0 +1,160 @@
1
+ from typing import Any
2
+
3
+ from cv_renderer.models import Bullet, CVData, Profile
4
+
5
+
6
+ def _resolve(variants: dict[str, str], variant: str) -> str:
7
+ return variants.get(variant) or variants.get("default", "")
8
+
9
+
10
+ def _filter_bullets(
11
+ bullets: list[Bullet],
12
+ focus: set[str],
13
+ deprio: set[str],
14
+ max_n: int,
15
+ ) -> list[str]:
16
+ focused: list[str] = []
17
+ neutral: list[str] = []
18
+ for b in bullets:
19
+ btags = set(b.tags)
20
+ if btags & focus:
21
+ focused.append(b.text)
22
+ elif not btags.issubset(deprio):
23
+ neutral.append(b.text)
24
+ return (focused + neutral)[:max_n]
25
+
26
+
27
+ def _resolve_bullets(
28
+ override: list[str] | None,
29
+ bullets: list[Bullet],
30
+ focus: set[str],
31
+ deprio: set[str],
32
+ max_n: int,
33
+ ) -> list[str]:
34
+ if override is not None:
35
+ return override
36
+ return _filter_bullets(bullets, focus, deprio, max_n)
37
+
38
+
39
+ def _filter_skill_items(items: list[Bullet], focus: set[str], deprio: set[str]) -> list[str]:
40
+ # Unlike job/project bullets, skill items are short and untagged-by-default:
41
+ # an item with no tags is generic and always kept, not dropped as "empty subset of deprio".
42
+ focused: list[str] = []
43
+ neutral: list[str] = []
44
+ for item in items:
45
+ tags = set(item.tags)
46
+ if tags & focus:
47
+ focused.append(item.text)
48
+ elif tags and tags.issubset(deprio):
49
+ continue
50
+ else:
51
+ neutral.append(item.text)
52
+ return focused + neutral
53
+
54
+
55
+ def apply_profile(cv: CVData, profile: Profile, labels: dict[str, str]) -> dict[str, Any]:
56
+ variant = profile.variant
57
+ focus = set(profile.focus_tags)
58
+ deprio = set(profile.deprioritize_tags)
59
+ max_b = profile.max_bullets_per_job
60
+ include_all = not focus
61
+
62
+ meta: dict[str, Any] = {
63
+ "name": cv.meta.name,
64
+ "title": profile.title_override or _resolve(cv.meta.title, variant),
65
+ "location": cv.meta.location,
66
+ "email": cv.meta.email,
67
+ "phone": cv.meta.phone,
68
+ "links": cv.meta.links.model_dump(),
69
+ }
70
+
71
+ experience: list[dict[str, Any]] = []
72
+ for job in cv.experience:
73
+ override = profile.experience_overrides.get(job.company)
74
+ bullets = _resolve_bullets(override, job.bullets, focus, deprio, max_b)
75
+ if not bullets:
76
+ continue
77
+ experience.append(
78
+ {
79
+ "company": job.company,
80
+ "title": _resolve(job.title, variant),
81
+ "location": job.location,
82
+ "start": job.start,
83
+ "end": job.end,
84
+ "description": job.description,
85
+ "bullets": bullets,
86
+ }
87
+ )
88
+
89
+ skills: list[dict[str, Any]]
90
+ if profile.skill_overrides is not None:
91
+ skills = [
92
+ {"category": category, "entries": entries}
93
+ for category, entries in profile.skill_overrides.items()
94
+ ]
95
+ else:
96
+ include_tags = focus | {"always"}
97
+ skills = []
98
+ for cat in cv.skills:
99
+ if profile.skill_categories is not None:
100
+ if cat.category not in profile.skill_categories:
101
+ continue
102
+ elif not include_all and not (set(cat.tags) & include_tags):
103
+ continue
104
+ entries = _filter_skill_items(cat.items, focus, deprio)
105
+ if not entries:
106
+ continue
107
+ skills.append({"category": cat.category, "entries": entries})
108
+
109
+ if profile.skill_categories is not None:
110
+ order = {name: i for i, name in enumerate(profile.skill_categories)}
111
+ skills.sort(key=lambda s: order.get(s["category"], 999))
112
+
113
+ projects: list[dict[str, Any]] = []
114
+ for proj in cv.projects:
115
+ override = profile.project_overrides.get(proj.name)
116
+ if override is None and not include_all and not (set(proj.tags) & focus):
117
+ continue
118
+ bullets = _resolve_bullets(override, proj.bullets, focus, deprio, max_b)
119
+ if not bullets:
120
+ continue
121
+ projects.append(
122
+ {
123
+ "name": proj.name,
124
+ "subtitle": proj.subtitle,
125
+ "year": proj.year,
126
+ "start": proj.start,
127
+ "end": proj.end,
128
+ "bullets": bullets,
129
+ }
130
+ )
131
+
132
+ if profile.project_order is not None:
133
+ order = {name: i for i, name in enumerate(profile.project_order)}
134
+ projects.sort(key=lambda p: order.get(p["name"], 999))
135
+
136
+ education: list[dict[str, Any]] = [
137
+ {
138
+ "institution": e.institution,
139
+ "degree": e.degree,
140
+ "location": e.location,
141
+ "start": e.start,
142
+ "end": e.end,
143
+ }
144
+ for e in cv.education
145
+ ]
146
+
147
+ return {
148
+ "meta": meta,
149
+ "about": profile.about_override or _resolve(cv.about, variant),
150
+ "experience": experience,
151
+ "skills": skills,
152
+ "projects": projects,
153
+ "education": education,
154
+ "additional": cv.additional.model_dump(),
155
+ "sections": profile.sections,
156
+ "lang": profile.lang,
157
+ "labels": labels,
158
+ "font_family": profile.font_family,
159
+ "font_sizes": profile.font_sizes.model_dump(),
160
+ }