Planet Python
Last update: July 02, 2026 07:48 PM UTC
July 02, 2026
EuroPython
EuroPython 2026 Job Opportunities from Our Sponsors
As EuroPython 2026 approaches and we prepare for another exciting edition, we’d like to thank our community and sponsors for their continued support.
We’re also pleased to share some fantastic job opportunities from our sponsors. Take a look and perhaps you’ll discover your next role.
ActiveCampaign
Software Engineer, Agent Development
About ActiveCampaign
ActiveCampaign is the autonomous marketing platform for people at the heart of the action. It empowers teams to automate their campaigns with AI agents that imagine, activate, and validate–freeing them from step-by-step workflows and unlocking limitless ways to orchestrate their marketing.
With AI, goal-based automation, and 1,000+ app integrations, agencies, marketers, and owners can build cross-channel campaigns in minutes–fine-tuned with billions of data points to drive real results for their unique business.
ActiveCampaign is the trusted choice to help businesses unlock a new world of boundless opportunities–where ideas become impact and potential turns into real results.
As a global multicultural company, we are proud of our inclusive culture which embraces diverse voices, backgrounds, and perspectives. We don’t just celebrate our differences, we believe our diversity is what empowers our innovation and success. You can find out more about our DEI initiatives here.
About the job
ActiveCampaign is seeking a Software Engineer to join our Kraków Hub and build production-ready AI agents for autonomous marketing. Our platform empowers businesses to automate their customer engagement — and we are now extending that with intelligent, agentic workflows that can reason, act, and adapt on behalf of our customers. Our first wave of agents is already in production. This role will drive the expansion, building the next generation of autonomous experiences across the full breadth of the platform.
You &aposll work at the intersection of solid software engineering and applied AI — designing, building, evaluating, and operating agents that run at scale in a production marketing automation platform. This means engineering rigor comes first: agents need to be reliable, observable, performant, and safe before they are clever.Our Kraków Hub houses multiple engineering teams — Forms, Integrations, Ecommerce, Agency, CRM, Mobile, CampaignsUI, and Content — working across different product pillars with a shared focus on building agentic workflows into all core functionalities across the platform. You will collaborate closely with these teams, understanding their domains and building agents that integrate seamlessly into the broader system,This is not a pure research or ML role. We need engineers who can build — end to end — from prompt design through backend orchestration to frontend integration, and who can ship, measure, and iterate fast. Our environment moves quickly — we value high ownership, tight feedback loops, and close cross-functional collaboration. If you thrive in a hands-on, high-pace setting where you build, ship, and iterate rapidly, this role is for you.
What Your Day Could Consist Of
- Designing and building production-grade AI agents and autonomous marketing workflows across the ActiveCampaign platform
- Implementing multi-step agentic flows involving LLM orchestration, tool use, and integration with platform services
- Working with LLM APIs (prompt engineering, response handling, evaluation) and building reliable, testable agent pipelines
- Designing and running agent evaluations — building eval frameworks, defining quality metrics, and continuously measuring agent accuracy, reliability, and performance in production at scale
- Monitoring and optimizing agent performance — latency, cost, token usage, error rates — and iterating rapidly based on production dataIterating on agent behavior based on user feedback, observability data, and quality metrics
- Leveraging AI-assisted development tools and practices in your daily workflow — you don&apost just build AI features, you build with AI
- Writing production-quality code in Python for agent services and backend orchestration
- Contributing to PHP backend and frontend (React/Ember) integration points where agents interact with the core platform
- Building and maintaining APIs, data flows, and service interfaces that agents depend on
- Working iteratively — shipping code fast, learning from production, and improving continuously
- Ensuring code quality through testing, code review, and adherence to engineering standards
- Participating in on-call rotation for incidents related to agent services
- Working closely with Product, Design, and domain engineering teams to define agent capabilities and user experiences
- Engaging in pairing and code reviews across teams
- Contributing to documentation of agent architectures, patterns, and best practices
- Maintaining regular overlap with US-based teams (Chicago timezone) to ensure alignment and tight collaboration across the organization
What We&aposre Looking For
- 2-4 years of experience in a software engineering role
- Hands-on experience building and running AI agents or agentic workflows in a production environment — not just prototypes or side projects
- Experience with agent evaluation and performance optimization — you know how to measure whether an agent is working well and how to make it better
- Solid backend development skills in Python; working familiarity with PHP is a plus
- Understanding of how to build reliable, testable, and observable systems
- Active practitioner of AI-driven development — you use AI coding assistants and agentic development tools as part of how you work, not just what you build
- Ability to work iteratively and ship code fast — you&aposre comfortable with rapid release cycles and learning from production
- Familiarity with DB technologies: MySQL, DynamoDB, Redis
- Strong problem-solving skills and ability to work across multiple codebases and technologies
- Fluent in English (B2 minimum)
- Willingness to work flexible hours with regular afternoon availability to overlap with US Central timezone
- Experience with agentic AI frameworks or multi-agent orchestration patterns
- Familiarity with Model Context Protocol (MCP) or tool-use patterns for LLM agents
- Experience building eval pipelines or quality measurement systems for LLM-based features
- Frontend experience with React or EmberFamiliarity with AWS, CI/CD practices, and observability tooling
- Understanding of prompt engineering, evaluation methodologies, or LLM fine-tuningExperience in SaaS, marketing automation, or CRM domain
What We Offer
- A front-row seat in ActiveCampaign&aposs AI-first transformation — you&aposll build the autonomous marketing agents customers interact with
- Collaboration with experienced engineers across the Kraków Hub, including senior and staff-level technical leadership
- An environment that values ownership, fast iteration, and learning by doing
- Active investment in AI-first engineering practices and tooling
- Hybrid work model from our Kraków office with flexibility to adjust working hours for collaboration with US-based teams (Chicago timezone overlap)
This is an exciting time to join ActiveCampaign as we build out our new office in Poland. You will be a large part of developing our office culture in this new Krakow hub location.
Perks And Benefits
At ActiveCampaign, we prioritize employees’ well-being and professional growth by cultivating a culture centered on collaboration and innovation. When you join our team, you’ll not only have the opportunity to make a significant impact, but also enjoy a range of benefits tailored to support your personal and career development.
Here Are Some Of The Benefits We Offer
- Comprehensive Health & Wellness: Top-tier benefits package that includes medical and dental benefits paid 100% by ActiveCampaign for you and 50% for your dependents, and reimbursements on vision expenses. In addition, employees receive complimentary access to telehealth services, and a free subscription to Calm.
- Growth & Development: Access to LinkedIn Learning, professional development programs, and career growth opportunities in a fast-growing organization.
- Generous Paid Time Off: Recharge and take the time you need to maintain work-life balance.
- Total Rewards: Pension scheme with matching up to 1.5% of your contribution, MultiSport Plus card to support your active lifestyle, home office stipend to cover your commuter or work from home expenses, and a four-week paid sabbatical with bonus after five years.
- Collaborative Culture: Work alongside brilliant, passionate colleagues in an environment that values innovation, teamwork, and mutual support.
ActiveCampaign is an equal opportunity employer. We recruit, hire, pay, grow, and promote no matter of gender, race, ethnicity, sexual orientation, marital status, political opinion, national origin, social origin, parentage, workers union membership, economic status, religion, age, health condition, disability or any other grounds protected by law.
Our Employee Resource Groups (ERGs) strive to foster a diverse inclusive environment by supporting each other, building a strong sense of belonging, and creating opportunities for mentorship and professional growth for their members.
We may use artificial intelligence (AI) tools to support parts of the hiring process, such as reviewing applications, analyzing resumes, or assessing responses and identifying potential inconsistencies or verification signals in application materials based on available information. These tools assist our recruitment team but do not replace human judgment. Final hiring decisions are ultimately made by humans. If you would like more information about how your data is processed, please contact us.
Compensation details listed in this posting reflect the base rate only and do not include bonus, equity, sales incentives or other role specific compensation that the role may be eligible for. ActiveCampaign believes in and is committed to equitable compensation practices. The salary range provided above is a good faith estimate of the pay range determined by the location associated with the job posting. The actual salary depends on a candidate’s skills, experience, and work location.
Apply here: https://jobs.lever.co/activecampaign/3e093aec-8540-49c7-93f1-719727c1191d/apply
Apify
Software Engineer for Fraud Prevention (TypeScript)
About Apify:
Apify is the largest marketplace of tools for AI. 40,000 Actors helping people and agents get real-time web data, track competitors, generate leads, or integrate their apps. Actors are built by a global creator community that now earns more than $1M every month.
Software Engineer for Fraud Prevention (TypeScript)
Job description:
As our Fraud Engineer, you will design and build systems to stop bad actors in real time. You&aposll investigate new attack patterns, deploy quick fixes, and create internal tools to handle incidents. It&aposs a hands-on role where you will work with security and product teams to keep our AI platform safe without slowing down legitimate users.
How to apply: https://jobs.ashbyhq.com/apify/7c93886a-9f1d-4ed9-ba71-d753c8f34c82?utm_source=career_page_apify
BCG X
Who We Are
Boston Consulting Group partners with leaders in business and society to tackle their most important challenges and capture their greatest opportunities. BCG was the pioneer in business strategy when it was founded in 1963. Today, we help clients with total transformation-inspiring complex change, enabling organizations to grow, building competitive advantage, and driving bottom-line impact.
To succeed, organizations must blend digital and human capabilities. Our diverse, global teams bring deep industry and functional expertise and a range of perspectives to spark change. BCG delivers solutions through leading-edge management consulting along with technology and design, corporate and digital ventures—and business purpose. We work in a uniquely collaborative model across the firm and throughout all levels of the client organization, generating results that allow our clients to thrive.
We Are BCG X
We&aposre a diverse team of more than 3,000 tech experts united by a drive to make a difference. Working across industries and disciplines, we combine our experience and expertise to tackle the biggest challenges faced by society today. We go beyond what was once thought possible, creating new and innovative solutions to the world&aposs most complex problems. Leveraging BCG&aposs global network and partnerships with leading organizations, BCG X provides a stable ecosystem for talent to build game-changing businesses, products, and services from the ground up, all while growing their career. Together, we strive to create solutions that will positively impact the lives of millions.
What You&aposll Do
Our BCG X teams own the full analytics value-chain end to end: framing new business challenges, designing innovative algorithms, implementing, and deploying scalable solutions, and enabling colleagues and clients to fully embrace AI. Our product offerings span from fully custom-builds to industry specific leading edge AI software solutions.
Our Forward Deployed AI Engineer and Senior Forward Deployed AI Engineer are part of our rapidly growing engineering team and help to build the next generation of AI solutions. You&aposll have the chance to partner with clients in a variety of BCG regions and industries, and on key topics like climate change, enabling them to design, build, and deploy new and innovative solutions. Additional responsibilities will include developing and delivering thought leadership in scientific communities and papers as well as leading conferences on behalf of BCG X.
We are looking for dedicated individuals with a passion for software development, large-scale data analytics and redefining organizations into AI led innovative companies. Successful candidates possess the following:
- Apply software development practices and standards to develop robust and maintainable software
- Actively involved in every part of the software development process
- Experienced at guiding non-technical teams and consultants in best practices for robust software development
- Optimize and enhance computational efficiency of algorithms and software design
- Motivated by a fast-paced, service-oriented environment and interacting directly with clients on new features for future product releases
- Enjoy collaborating in teams to share software design and solution ideas
- A natural problem-solver and intellectually curious across a breadth of industries and topics
- Fluency in both Italian and English
What You&aposll Bring
Requirements:
- Master&aposs or PhD degree program in Computer Science, Data Science, Statistics, Operations Research, or related field
Technologies
- Programming: Python, Java/Scala
- Frameworks/Libraries: TensorFlow, PyTorch, Scikit-learn, Keras
- Data Processing: NumPy, Pandas, Apache Spark, Dask
- Cloud Platforms: AWS (SageMaker, EC2), GCP (AI Platform), Azure (ML Studio)
- DevOps: Docker, Kubernetes, Terraform, Jenkins
- Data Visualization: Matplotlib, Seaborn, Plotly/Dash, Tableau
- Tools: Jupyter Notebooks, Google Colab, Git (GitHub/GitLab), MLflow, TensorBoard
- Deployment: Flask, FastAPI, TensorFlow Serving, Streamlit
Apply: https://careers.bcg.com/global/en/job/55983/Forward-Deployed-AI-Engineer-Italy-BCG-X
Bloomberg
Senior Software Engineer - AI App Enablement & Observability
📍Location: Dublin
Description & Requirements
Platform Engineering builds the core platforms, tooling, and paved roads that Bloomberg engineers rely on to ship reliable, secure, and high-performing systems at scale.The AI App Enablement & Observability team accelerates how AI products are built across Bloomberg Industry Group. Our mission is to make AI systems reliable, performant, cost-efficient, and continuously improving through platform tooling, deep observability, and automated feedback loops.We build developer-facing platforms and workflows that enable teams to experiment, deploy, and operate AI and agent-based systems with confidence. This includes LLM gateways, agent platforms, benchmarking systems, telemetry pipelines, and self-improving infrastructure that closes the loop between observability and action. We emphasise strong developer experience, intuitive APIs/SDKs, and end-to-end ownership.
What’s in it for you?
You will help define how Bloomberg Industry Group builds and operates AI systems at scale by working on platforms that:
- Accelerate AI product development through reusable tooling and paved roads
- Provide end-to-end observability across AI systems (models, agents, pipelines, applications)
- Enable self-improving systems through telemetry-driven feedback loops
- Optimise cost, performance, and reliability of AI workloads
- Support both production AI systems and internal engineering agents
You’ll collaborate across AI product, infrastructure, and platform teams to deliver foundational systems.
We’ll trust you to:Platform & Enablement
- Build and evolve AI platform tooling (e.g., developer workflows, benchmarking systems)
- Design developer-friendly APIs, SDKs, and interfaces
- Contribute to systems across the Model Development Lifecycle (experimentation, deployment, evaluation)
Observability & Telemetry
- Build and operate observability platforms and telemetry pipelines (logs, metrics, traces, events)
- Provide visibility into latency, token usage, cost, quality, drift, and reliability
- Define instrumentation standards, schemas, and conventions
- Implement distributed tracing using modern approaches (e.g., OpenTelemetry)
AI System Insights & Debugging
- Enable end-to-end debugging of AI and agent workflows (model calls, tool usage, retrieval, orchestration)
- Build benchmarking, regression detection, and performance analysis capabilities
- Support observability for both production systems and internal engineering agents
Closed-loop Optimization & Automation
- Develop systems that turn telemetry into action (automated experimentation, regression detection, alerting)
- Build feedback loops that continuously improve model quality and system behavior
- Enable self-healing and self-optimising workflows
Cost, Performance & Reliability
- Build tooling for cost visibility, forecasting, and optimization
- Define SLOs, alerting, and performance tuning practices
- Improve reliability and scalability of AI infrastructure
Ownership & Collaboration
- Own projects end-to-end (RFCs, architecture, implementation, rollout, production support)
- Partner with AI teams to drive adoption of platform tooling and standards
- Produce high-quality documentation and improve developer experience
You’ll need to have:
- Demonstrated experience building production software or platform systems
- Strong engineering fundamentals with distributed systems or backend platforms
- Experience or strong interest in observability and debugging complex systems
- Experience or strong interest in AI/ML systems, LLMs, or agent-based architectures
- Strong ownership mindset and ability to drive ambiguous problems to production
- Hands-on experience with modern agentic coding tools and multi-model workflows
- Working knowledge of agent architecture internals (context engineering, tool loops, sub-agent orchestration)
We’d love to see:
- Experience with OpenTelemetry and modern observability ecosystems, including instrumentation, collectors, exporters, and tools like Prometheus, Grafana, and tracing/log systems
- Experience designing and operating telemetry pipelines, including sampling, retention, cardinality, and cost tradeoffs, as well as integrating observability into CI/CD and developer workflows
- Familiarity with AI/agent frameworks, including instrumentation of LLM calls, tool usage, workflows, and evaluation signals (quality metrics, benchmarking, regression detection)
- Experience building cost monitoring, forecasting, and optimization systems for AI workloads
- Familiarity with cloud and infrastructure tooling (e.g., AWS, Azure, Kubernetes, Terraform)
- Experience with agentic infrastructure concepts such as MCP servers, hooks, skills, subagents, sandboxing, and persistent memory patterns
- Active engagement with the agentic engineering frontier, including emerging patterns (e.g., harness vs. model, review debt, feedback loops)
- Demonstrated agent-native development practices (iterating with agents using testing, verification, and feedback loops)
- Strong security awareness for autonomous systems, including sandboxing, prompt injection risks, credential exposure, and guardrails
If indicated, please note that years of experience are a guide; we will consider applications from all candidates who can demonstrate the skills necessary for the role.Discover what makes Bloomberg unique - watch our podcast series for an inside look at our culture, values, and the people behind our success.
Apply here: https://bloomberg.avature.net/careers/Public?jobId=18854
Hudson River Trading (HRT)
Quantitative Latency Engineer
📍Austin, TX, United States; Chicago, Illinois, United States; London, United Kingdom; New York, NY, United States; Singapore
Hudson River Trading (HRT) is seeking curious, thoughtful engineers who enjoy working with data and solving real-world technical problems to join our growing Market Structure Analysis team. In this role as a Quantitative Latency Engineer, you’ll apply data-driven methodologies to understand and optimize trading technology and real-time interactions with financial markets across the globe, spanning traditional and crypto exchanges. No prior finance experience is needed!
Responsibilities
- Analyze time series network and exchange protocol captures
- Become familiar with the details of specific markets, attend presentations and liaise with exchange counterparts
- Research exchange features, capabilities, and architecture
- Automate collection and visualization of metrics that quantify efficacy of exchange communication
- Formulate and conduct controlled experiments that measure impact of calculated changes to HRT’s trading infrastructure
- Communicate ideas, requirements, and results across disparate teams
- Improve fill rate of our hardware-based trading strategy
- Reduce incidence of cancel-reject responses
- Investigate and report details of various latency-sensitive exchanges
Profile
- You possess a degree in Data Analytics or a related field
- You can collect and interpret network and/or financial market data
- You have professional experience in latency reduction, preferably in finance
- You have a basic understanding of proprietary trading and exchange technologies
Skills
- Proficiency in data analytics including statistics, data visualization, and working with large data sets
- Basic understanding of TCP and UDP network protocols
- Extensive experience with Python and relevant data libraries (Pandas, Numpy/Scipy)
- Some familiarity with the details of modern computer systems and networks
- Experience with real time exchange market data and order entry a plus
The estimated base salary range for this position is 200,000 to 300,000 USD per year (or local equivalent). The base pay offered may vary depending on multiple individualized factors, including location, job-related knowledge, skills, and experience. This role will also be eligible for discretionary performance-based bonuses and a competitive benefits package.
Culture
Hudson River Trading (HRT) brings a scientific approach to trading financial products. We have built one of the world&aposs most sophisticated computing environments for research and development. Our researchers are at the forefront of innovation in the world of algorithmic trading.
At HRT we welcome a variety of expertise: mathematics and computer science, physics and engineering, media and tech. We’re a community of self-starters who are motivated by the excitement of being at the cutting edge of automation in every part of our organization—from trading, to business operations, to recruiting and beyond. We value openness and transparency, and celebrate great ideas from HRT veterans and new hires alike. At HRT we’re friends and colleagues – whether we are sharing a meal, playing the latest board game, or writing elegant code. We embrace a culture of togetherness that extends far beyond the walls of our office.
Feel like you belong at HRT? Our goal is to find the best people and bring them together to do great work in a place where everyone is valued. HRT is proud of our diverse staff; we have offices all over the globe and benefit from our varied and unique perspectives. HRT is an equal opportunity employer; so whoever you are we’d love to get to know you.
Please be advised: Use of AI tools during interviews or assessments is strictly prohibited, unless otherwise instructed or agreed upon. We employ various methods to evaluate the authenticity of candidate responses. If we determine that AI assistance was used during any stage of the hiring process, we reserve the right to immediately disqualify your candidacy or rescind any job offers extended.
Apply here: https://www.hudsonrivertrading.com/hrt-job/quantitative-latency-engineer/
Modal
The production cloud for AI. Run inference, training, batch processing and sandboxes with sub-second cold starts, instant autoscaling across thousands of GPUs and a developer experience that feels local.
Member of Technical Staff - Python SDK
AI needs a new infrastructure layer. We&aposre building it at Modal.
Every era of computing brought new workloads that previous infrastructure couldn&apost support: mainframes, databases, and the cloud. Each time, the company that rebuilt the layer underneath defined the decade. AI is no different, except it touches everything instead of one slice, and the window to build the layer underneath it is open right now.
Our customers include category-defining companies like Lovable, Ramp, Cognition, DoorDash, and Suno. They rely on Modal for instant GPU access, sub-second container starts, and native storage, so it&aposs simple to serve low-latency inference, fine-tune models, and access production-ready sandboxes at scale.
We recently raised a $355M Series C at a $4.65B valuation, led by General Catalyst and Redpoint Ventures. We&aposve crossed $300M+ ARR and grown fivefold since September.
Our team includes creators of popular open-source projects (e.g.,Seaborn, Luigi), academic researchers, international olympiad medalists, and experienced engineering and product leaders with decades of experience.
The Role:
We’re looking for strong engineers with experience building developer tools that users love to work with. Our ideal candidate is someone with a demonstrated drive to build beautiful interfaces that enhance developer productivity.
Requirements:
- 5+ years of experience developing high-quality Python libraries with broad user-bases, ideally including some experience maintaining open-source software.
- Knowledge of advanced Python features, especially async programming.
- A strong product sense that manifests as a focus on developer ergonomics and productivity.
- A high level of customer empathy, good communication skills, and an openness to working directly with our users to help solve their problems.
- Ability to participate in on-call rotation and respond to production incidents.
- Ability to work in-person in our NYC or Stockholm office.
- Any of the following would be a plus:
- Familiarity with modern data / ML / AI tools and workflows
- Experience with Typescript, Go, or Rust
How to apply:
https://jobs.ashbyhq.com/modal/265d6127-dd34-433b-819a-1f935572c7d8
Numberly
SRE - Site Reliability Engineer
📍Hybrid - Paris
Numberly is recognized as one of the world&aposs leading data marketing specialists with nearly 500 employees and 11 offices worldwide serving more than 300 clients (L&aposOréal, Campari, Colgate, Nestlé, HSBC...). By putting technology to work for brands and consumers, Numberly is at the heart of business growth and everyone&aposs desire for more sustainable and relevant marketing. Numberly leverages the latest advances in data processing, analysis and activation, incorporating artificial intelligence technologies. This approach is part of a virtuous circle in which business competitiveness goes hand in hand with greater respect for privacy and data protection.
To achieve this, Numberly has always mastered, developed, and operated its own on-premise technical platforms thanks to the expertise of its in-house teams, without overlooking the Cloud when relevant. From development to datacenter hosting and network connectivity, everything is designed, built, maintained, and secured by our teams and we are proud of it.
Numberly is looking for a Site Reliability Engineer to design, operate, and improve the reliability of its infrastructure in order to always better serve its clients.
SRE Responsibilities
- Design, build, and operate highly available, robust, and scalable system architectures.
- Ensure the availability, performance, and reliability of production services.
- Know how to leverage existing tools (open-source or internal) and develop new solutions when relevant.
- Collaborate with other technical teams to proactively anticipate and resolve scaling challenges.
- Participate in the continuous improvement of operations practices (automation, monitoring, observability, incident management).
Qualifications
- Master’s degree (Bac +4/+5) from a university, engineering school, or equivalent.
- 3 to 5 years of experience in operating production systems.
- Strong troubleshooting and problem-solving skills.
- Good communicator, capable of popularizing their work, defending their ideas, and listening to others.
- Willingness to grow and help others grow, both technically (meetups, internal training) and humanly.
- Strong understanding of Linux internals.
- Fluent in French and proficient in English.
Technical Environment
- Physical infrastructure: 3 datacenters located in the Paris region
- Servers: +500
- Automation: Ansible, Terraform
- CI/CD: GitLab
- Virtualization: Proxmox
- Containers: Kubernetes (on-premises Kubespray and Talos, AWS EKS, Azure AKS)
- Load-balancing: HAProxy, OpenResty (nginx), Envoy
- Monitoring: Prometheus, Thanos, Kafka, Elasticsearch, Graylog
- Tracing: Sentry
- Languages: Python, Go, Rust
- Server OS: Ubuntu / Debian
- User OS: Windows / MacOS / Linux
- APIs: REST
- Cloud: AWS, Azure
- Databases: PostgreSQL, Hadoop, MSSQL, ScyllaDB, MongoDB
Security Tools & Frameworks
- MDM: Intune, Iru (Kandji)
- Logs: Kafka, Graylog, Vector, CrowdSec
- IDS/IPS: Falco
- EDR: HarfangLab, Microsoft Defender for Endpoint
- Scanning: Ivre, Burp Suite
- SAST: GitLab SAST, Semgrep, etc.
- KMS/PKI: HashiCorp Vault
- Containers: Kyverno, Harbor
Additional information
- At Numberly, we share a passion for transmission: weekly internal talks, meetings with expert professionals in their field, continuous learning.
- Fast and powerful onboarding, in particular thanks to: the mentor assigned to each newcomer; to Live my life in different teams; Happy Meetings: monthly internal meetings to meet up with all our teams around the world and share group news.
- We cultivate freedom of speech which allows everyone to participate in the development of the group.
- We act positively on our ecosystem through 1000mercis impacts and via our activities which create value in the Open Internet and contribute to the enrichment of Open Source.
- Numberly is an actor of diversity with a gender equity score of 97/100.
- Numberly is ISO/IEC 27001:2023 certified; this certification recognizes compliance with the highest standards in information security.
- Numberly is an international environment with more than 30 nationalities in our teams.
- Offices in the image of each of the teams, a generous library, a large fully equipped music studio, two cats, vermicomposting, the possibility of bringing your pet and space for bicycles! In each kitchen: coffee, tea, unlimited infusions and also mystery lunches, Wellpass (ex-Gymlib) partnership, sports classes and parties (often in disguise).
- Possible remote working days.
- Swile card (meal vouchers).
- Possible mobility in our various offices abroad.
- Numberly welcomes people with disabilities.
Revolut
Mid/Senior Software Engineer (Python)
📍Remote: Cyprus · Czech Republic · Poland · Porto · Portugal · Romania · Serbia · Spain · Sweden · UAE · UK
About Revolut
People deserve more from their money. More visibility, more control, and more freedom. Since 2015, Revolut has been on a mission to deliver just that. Our powerhouse of products — including spending, saving, investing, exchanging, travelling, and more — help our 75+ million customers get more from their money every day.
As we continue our lightning-fast growth, 2 things are essential to our success: our people and our culture. In recognition of our outstanding employee experience, we&aposve been certified as a Great Place to Work™. So far, we have 13,000+ people working around the world, from our offices and remotely, to help us achieve our mission. And we&aposre looking for more brilliant people. People who love building great products, redefining success, and turning the complexity of a chaotic world into the simplicity of a beautiful solution.
About the role
Our Technology team builds the systems and experiences that keep Revolut moving. From the infrastructure behind our innovative app to the features used by millions of people around the world, they bring sharp thinking, speed, and a focus on meaningful impact to everything they do.
We’re looking for a Python Engineer who can write high-quality code and build innovative solutions for heavily regulated financial systems.
Whether designing our own chatbot or creating automated financial crime quality controls in just a few weeks, our engineering projects are varied. You’ll collaborate on a product team with Data Scientists, Analysts, Engineers, Product Owners, and Operations Managers to deliver the most value to our customers.
Up to shape what&aposs next in finance? Let&aposs get in touch.
What you’ll be doing
- Building APIs and jobs and data pipelines, making sure they&aposre properly designed and scaled according to business needs
- Writing event consumers to build data models for new flows and processes
What you&aposll need
- 5+ years of experience as a Software Engineer
- 3+ years of experience engineering with Python as your primary language
- An academic background in STEM
- Fluency in Python, SQL, and other OOPLs
- Experience with API development and integration
- A practical understanding of distributed systems
- The ability to write concurrent code in IO/CPU bound situations
- Experience with Docker, K8s, Ansible, Teamcity, monitoring, and alerting
Nice to have
- Experience with prototyping and sketching
- Multiple side projects or open source contributions
- Exposure to GCP
How to apply: https://revolut.la/EuroPython2026_MidSeniorSoftwareEngineerPython
Python Software Foundation
Thinking about running for the PSF Board? Let’s talk!
PSF Board elections are a chance for the community to choose representatives to help the PSF create a vision for and build the future of the Python community. This year, there are 4 seats open on the PSF Board. Check out who is currently on the PSF Board on our website. (Cheuk Ting Ho, Christopher Neugebauer, Denny Perez, and Georgi Ker are at the end of their current terms.)
Office Hours Information
This year, the PSF Board is dedicating a few of their regular Office Hour sessions on the PSF Discord to the topic of the election. This is your chance to connect with current board members to ask questions and learn more about what being a part of the Board entails.
The two upcoming Office Hour sessions will be dedicated to the topic of the election:
We welcome you to join the PSF Discord to participate in Office Hours. The server is moderated by PSF Staff and locked between office hours sessions. If you’re new to Discord, check out some Discord Basics to help you get started.
Who runs for the Board?
Who runs for the board? People who care about the Python community, who want to see it flourish and grow, and also have a few hours a month to attend regular meetings, serve on committees, participate in conversations, and promote the Python community. We're looking for candidates with a diverse range of skills and backgrounds, including leadership experience, fundraising knowledge, non-profit familiarity, and event organizing. Technical expertise, a record of collaboration, and experience speaking or teaching in the Python community are also all qualities we hope to see in Board members.
Want to learn more about being on the PSF Board? Check out the following resources to learn more about the PSF, as well as what being a part of the PSF Board entails:
- Life as Python Software Foundation Director video on YouTube
- FAQs About the PSF Board video on YouTube
- Our past few Annual Impact Reports:
You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement too. Nominations open on Tuesday, July 28th, 2:00 pm UTC, so you have time to talk with potential nominees, research the role, and craft a nomination statement for yourself or others. Take a look at last year’s nomination statements for reference.
Nomination information
You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement too. Nominations open on Tuesday, July 28th, 2:00 pm UTC, so you have time to talk with potential nominees, research the role, and craft a nomination statement for yourself or others. The nomination period ends on Tuesday, August 11th, 2:00 pm UTC. There will be a ‘call for nominations’ blog post with more information and resources about nominations coming soon.
July 01, 2026
Tryton News
Tryton News July 2026
Once again this month the community put most of its energy into fixing bugs, refining existing behaviour, and improving performance on top of our last LTS release 8.0. In addition, we are happy to present a selection of new features and documentation updates in this newsletter.
For an in depth overview of the Tryton issues please take a look at our issue tracker or see the issues and merge requests filtered by label.
Changes for the User
Sales, Purchases and Projects
We now move the warehouse and the shipping date of the sale to a different page, which keeps the sale form a little more compact.
Accounting, Invoicing and Payments
The entry for invoice payment methods is now moved under the invoice payments menu, so the menus for invoices and invoice payments are no longer mixed.
Accounts with the setting party required are now grouping their account move lines per party in the general ledger.
The lines of the general ledger account are now ordered consistently with the cumulative balance.
We update the version of Stripe used by the payment gateway to the latest one.
Stock, Production and Shipments
On the stock move form, the cost fields are now displayed more cleanly: the commission price is only shown when it is set.
User Interface
The binary and image widgets now accept a custom filters attribute, so administrators can restrict the file types users see when picking or saving a file.
When downloading a product image, the file is now suffixed with the proper .jpg extension, which makes the file easier to open from a folder.
For tall screens, the maximal height of tree views and list forms is now relative to the viewport instead of a fixed pixel value, so the available vertical space is no longer wasted.
In the domain parser, selection values are now completed using a “contains” matcher instead of “starts with”, which makes it easier to write a filter when several options share a common prefix.
Searching by record name on a contact mechanism now also matches the contact mechanism’s own name field, so users can find a phone number or e-mail by typing a label like “office” or “personal”.
New Documentation
The help text of the tax rule fields on the party is now explicit about what happens when the field is left empty.
The usage of the active_test context key in ModelSQL.search_domain is now documented in the reference manual.
The module tutorial has been updated to match the layout of the project skeleton generated by cookiecutter, so newcomers can follow it without surprises.
New Releases
We released bug fixes for the currently maintained long term support series
8.0 and 7.0, and for the penultimate series 7.8.
Changes for Implementers and Developers
The WSGI dispatcher now handles exceptions raised from within the with_pool decorator itself: unexpected exceptions are logged at exception level and their traceback is written to the WSGI wsgi.errors stream, while exceptions used as HTTP responses have their description converted to a plain string.
This text is produced by utilising minimax-m3.
2 posts - 1 participant
Python Software Foundation
Python Packaging Council Inaugural Election Dates
With the recent approval of PEP 772 – Packaging Council governance process, a new Python Packaging Council (PPC) is being established with broad authority over packaging specifications and the mandate to coordinate Python packaging efforts. The election of the inaugural PPC will be held in parallel to the 2026 Python Software Foundation (PSF) Board election.
What is the Python Packaging Council?
The PPC will be the technical decision making body for the interoperability specifications affecting how Python packages are built, distributed, and installed.
The council will also serve as a coordinating body for the Python packaging ecosystem, working with many stakeholders from the wider Python community toward an ever-improving packaging user experience. This will include the maintainers of various packaging tools like the Python Packaging Authority (PyPA), the Python core team, the Python Steering Council, and the PSF.
Election Overview
The 2026 inaugural election fills all five seats on the PPC. The two candidates receiving the highest number of votes shall be designated Cohort A with a two-year term, and the three candidates receiving the next highest number of votes shall be designated Cohort B with a one-year term.
In future elections, each cohort will be elected for a full two-year term in alternating years, so that roughly half of the PPC turns over each cycle.
Election Timeline
The PPC election follows the same timeline as the PSF Board election:
Nominations open: Tuesday, July 28th, 2:00 pm UTC
Nomination cut-off: Tuesday, August 11th, 2:00 pm UTC
Announce candidates: Thursday, August 13th
Voter affirmation cut-off: Tuesday, August 25th, 2:00 pm UTC
Voting start date: Tuesday, September 1st, 2:00 pm UTC
Voting end date: Tuesday, September 15th, 2:00 pm UTC
Voting
You must be a Contributing, Supporting, or Fellow member by August 25th and affirm your intention to vote to participate in this election.
Check out the PSF membership page to learn more about membership classes and benefits. You can affirm your voting intention by following the steps in the PSF’s video tutorial:
Log in to psfmember.org
Choose “Your Memberships” page at the top right to check your eligibility to vote (You must be a Contributing, Supporting, or Fellow member)
Choose “Voting Affirmation” page at the top right
Select your preferred intention for voting in 2026 (which now includes a second affirmation regarding your intention to vote in the PPC election)
Click the “Submit” button
Like the PSF Board elections, casting a vote in a PPC election will automatically affirm your intention to participate in the next PPC election.
If you have questions about membership, please email pc-elections@python.org.
Election communications from psfmember.org
PSF Members should review their communication preferences on psfmember.org if you would like to opt in or out of receiving emails about the PSF Board, PPC elections, or both. Here’s how:
Log in to psfmember.org
Navigate to your “Profile” page
Click the “Name and Address” tab
Scroll down, designate your preferences
Click submit
If you had previously opted out of communications from the PSF through psfmember.org and would like to review or change your preference, we encourage you to update them using the instructions above. The PSF only sends a handful of election and fundraising related communications every year via psfmember.org.
Running for the Packaging Council
Do you have a vision for improving the Python packaging experience? Do you make the tools used to build and consume Python packages? Are you passionate about building communities, consensus, and standards focused on the user experience? If these resonate with you, and you have the time to attend regular meetings and participate in the standardization process, you should consider running for the inaugural PPC!
We're looking for candidates who can build bridges between projects and communities, who enjoy working with a very large community of passionate volunteers, and have a willingness to represent the wider community ahead of any single tool, project, or employer. We also welcome candidates who have a diverse set of skills and experiences, including open-governance experience, community stewardship, fundraising knowledge, and (of course!) technical expertise in Python packaging and distribution.
PEP 772 does provide non-binding operational suggestions, which hint at how the council could function. As this is the inaugural PPC, the individuals serving on it will be establishing the initial operating procedures, scope, interests, and agenda that future councils will build upon. Notably, "establishing specific processes for [the] Packaging Council and PyPA relationship" is something that the inaugural Packaging Council is expected to do.
You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement too. Nominations open on Tuesday, July 28th, 2:00 pm UTC, so you have time to talk with potential nominees, research the role, and craft a nomination statement for yourself or others. Remember, nominees must themselves be PSF voting members, and nomination statements must include information about the nominee’s relevant affiliations.
June 30, 2026
Anwesha Das
CRA Stewarship in Ansible project
CRA, EU Cyber Resilience Act, has stirred a lot of discussion in the Open Source Communities. Will my project be usable in EU anymore? What are my responsibilities as a developer of open source software? My software is shipped with a commercial software, does it make me a manufacturer? Open Source Community is dealing with a lot of confusion and qurries relating to EU Cyber Resilience Act. I am no different especially the deadline coming in next few months.
Red Hat has formally identified with the role of Open Source Steward for Ansible project. We, at Ansible community divided the complaince jounry in the following 4 phases :
- Gap analysis
- Implementation
- Communication
- Plan the next phase
Gap analysis
Finding out
- What does the law says?
- Requirement to be CRA compliant (for the role of steward)
- What does it mean to CRA steward compliant for Ansible project?
- Find out the gaps under CRA in the Ansible project
Implemention
The next phase for Ansible is implementation.
CRA should be viewed by the lenses of security. An opportunity to make the project secure by default and not the afterthought. With these intent earlier this year I, posted in Ansible Forum
- EU Cyber Resilience Act (CRA) and what it means for Ansible?
- Security as Default, Not an Afterthought: The CRA&aposs Real Message
As part of this work (as a member of the Ansible community and PE engineering team at Red Hat), we filed the following PRs to be reviewed by the community :
In the coming weeks and months you will read more on this topic from me.
PyCoder’s Weekly
Issue #741: Root Loggers, PEP 832, Django Tasks, and More (2026-06-30)
#741 – JUNE 30, 2026
View in Browser »
Please Don’t Hijack My Python Root Logger
Some Python libraries configure the root logger at import time, which can silently override application logging choices. This article walks through the logger hierarchy, shows why libraries should use named loggers with NullHandler, and gives practical patterns that keep logging behavior under the application’s control.
REDOWAN DELOWAR • Shared by Redowan Delowar
Why I Wrote PEP 832: Virtual Environment Discovery
PEP 832 proposes a way to describe where your virtual environment is so that your tools can look in the right place. Although a relatively simple proposal it has caused some contention in the community. This post by Brett, the PEP’s author, describes his reasoning.
BRETT CANNON
An Open-Source Code Reviewer That Runs on Any LLM
Your agents open PRs faster than you can review them. pr-af is a multi-agent reviewer that drops into GitHub Actions and runs on the model you already use — open-source, self-hosted, or closed. Every finding ships with evidence. Star it on GitHub →
AGENTFIELD sponsor
Django Tasks: Exploring the Built-in Tasks Framework
Explore Django Tasks, the new built-in framework in Django 6.0, and run background jobs with @task, named queues, and a lightweight alternative to Celery.
REAL PYTHON
Articles & Tutorials
Astral Joins OpenAI
OpenAI recently acquired Astral, the company behind uv, Ruff, and ty. And if your first thought was ‘wait, is uv toast?’, you are not alone. Talk Python interviews Charlie Marsh and they talk about the acquisition experience and how they’re shipping more code than ever.
TALK PYTHON podcast
The Fastest Python Struct?
An adventure in Python struct benchmarking: slotted class, NamedTuple, dataclass, attrs, msgspec, record-type, and a new C extension based on record-type and msgspec. Focus is on import-time, type-construction, memory, and instantiation cost.
JP HUTCHINS
Codex for Python Developers: A Hands-on Live Course
Build a complete Python project from an empty directory with OpenAI’s Codex in this two-day live course (July 11-12). You’ll scaffold, build, debug, and ship a real CLI app using an agent that works inside your codebase. See the Full Curriculum →
REAL PYTHON sponsor
Write a Coding Agent From First Principles
Learn how to write a coding agent in this Python tutorial that teaches how to interact with an LLM through an API, how to manage the conversation context, and how to do tool calling.
RODRIGO GIRÃO SERRÃO • Shared by Rodrigo Girão Serrão
Free Threading Internals: PyMutex
This is part 3 in an series about Free Threading internals (removal of the GIL) and talks about PyMutex, a one byte lock with fast inlineable lock and unlock functions.
VICTOR STINNER
uv in Production: The Speed Is Real, the Integration Isn’t Free
Oleg’s work moved their tooling from pip to uv and lived with it for ~90 days. They discovered that the speed is real, but that doesn’t mean there aren’t complications.
OLEG TSVETKOV • Shared by Oleg
Using LlamaIndex for RAG in Python
Learn how to set up LlamaIndex, load your data, build and persist an index, and run queries to get grounded answers with RAG in Python.
REAL PYTHON course
Python for Data Analysis: A Practical Guide
In this tutorial, you’ll learn data analysis with Python by following a structured workflow with pandas, Matplotlib, and scikit-learn.
REAL PYTHON
When to Use classmethod, staticmethod, or Instance Methods
A simple decision rule for when to use a @classmethod, a @staticmethod, or a plain instance method in Python.
BOB BELDERBOS
7 More Common Mistakes in Architecture Diagrams
A rundown of seven more common mistakes in system architecture diagrams and how to fix them
BILLY PILGER
Projects & Code
pyappdist: Turn a Python App Into a Native Installer
GITHUB.COM/ATSUOISHIMOTO • Shared by Atsuo Ishimtoo
flawed: Framework-Aware Static Analysis for Python Web Apps
GITHUB.COM/EXECVEAT • Shared by Andrew Konstantinov
pytest-mrt: Catch Database Migration Rollback Failures
GITHUB.COM/CROC100 • Shared by JUNEBEOM BAEK (croc)
linkedin2md: Export LinkedIn Data to Structured Markdown
GITHUB.COM/JUANMANUELDAZA • Shared by Juan Manuel Daza
Ozark: Examine Python ASTs, & Bytecode in Your Browser
FROMSCRATCHCODE.COM • Shared by Tyler Green
Events
Canberra Python Meetup
July 2, 2026
MEETUP.COM
Sydney Python User Group (SyPy)
July 2, 2026
SYPY.ORG
Python Leiden User Group
July 2, 2026
PYTHONLEIDEN.NL
Python Norte 2026
July 3 to July 6, 2026
PYTHONNORTE.ORG
Melbourne Python Users Group, Australia
July 6, 2026
J.MP
Happy Pythoning!
This was PyCoder’s Weekly Issue #741.
View in Browser »
[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]
Django Weblog
Keeping Up with the Django Community
The Django community runs on volunteer effort spread across several different groups of people. Most of it is public, but it isn't always easy to find.
Here are the public places to find it:
- Weekly DSF Office Hours (website)
- DSF Board updates (website, GitHub repo)
- Steering Council (forum thread, GitHub repo)
- Website Working Group (forum thread)
- Online Community Working Group (forum thread)
- Django Fellow Reports (Jacob, Natalia, Sarah)
DSF Office Hours
Most of these are places to read and follow along, but one invites you to show up in real time, the DSF Office Hours. From the website:
These are weekly office hours to work on anything related to the DSF. At least one board member will try to attend each week. Office hours take place every Wednesday at 6:00 PM UTC.
All you need to do is bring something DSF-related to work on. This is intentionally broad, as long as it's vaguely DSF-related you're welcome to come. It's not a general-purpose Django coding session (you're welcome to be writing code but it should be related the DSF, e.g. working on djangoproject.com or something).
They are friendly and casual. People are welcome to bring topics and discussions for everyone to cover. I personally find them helpful in staying on top of community news and finding impactful ways to contribute to the community.

To help set expectations, here's a list of what we discussed in the latest office hour:
- Executive Director position and fundraising
- Marketing / broadcasting office hours to the rest of the community
- Analytics on the website
- Community updates (Steering Council, Djangonaut Space)
- New prospectus discussion for DSF sponsors
- djangoproject.com website updates to reflect new prospectus pricing
- Google Summer of Code Working Group charter
- Contributor experience in Django
Each meeting the topics will change based on who is there and what's new, but if this sounds interesting to you, we'll see you next Wednesday!
Python Software Foundation
PSF Board Election Dates for 2026
Python Software Foundation (PSF) Board elections are a chance for the community to choose representatives to help the PSF create a vision for and build the future of the Python community. This year, there are 4 seats open on the PSF Board. Check out who is currently on the PSF Board on our website. (Cheuk Ting Ho, Christopher Neugebauer, Denny Perez, and Georgi Ker are at the end of their current terms.)
The recent approval of the Packaging Council (PPC) through PEP 772 means that the PPC election will be held in parallel to the PSF Board election. For the first PPC election, communications will be published on the PSF blog. Once the first PPC has been established, they will define the standard lines of communication and more PPC election process specifics for the future. More information on the PPC election coming soon.
Board Election Timeline
- Nominations open: Tuesday, July 28th, 2:00 pm UTC
- Nomination cut-off: Tuesday, August 11th, 2:00 pm UTC
- Announce candidates: Thursday, August 13th
- Voter affirmation cut-off: Tuesday, August 25th, 2:00 pm UTC
- Voting start date: Tuesday, September 1st, 2:00 pm UTC
- Voting end date: Tuesday, September 15th, 2:00 pm UTC
Voting
You must be a Contributing, Supporting, or Fellow member by August 25th and affirm your intention to vote to participate in this election. Reminder: If you were formerly a Managing member, your membership type was changed to Contributing per 2024’s Bylaw change that merged Managing and Contributing memberships.
Check out the PSF membership page to learn more about membership classes and benefits. You can affirm your voting intention by following the steps in our video tutorial:
- Log in to psfmember.org
- Choose “Your Memberships” page at the top right to check your eligibility to vote (You must be a Contributing, Supporting, or Fellow member)
- Choose “Voting Affirmation” page at the top right
- Select your preferred intention for voting in 2026 (which now includes a second affirmation regarding your intention to vote in the PC election)
- Click the “Submit” button
Per another recent Bylaw change that allows for simplifying the voter affirmation process by treating past voting activity as intent to continue voting, if you voted last year, you will automatically be added to the 2026 voter roll. Please note that if you removed or changed your email on psfmember.org, you may not automatically be added to this year's voter roll.
If you have questions about membership, please email psf-elections@pyfound.org.
Election communications from psfmember.org
PSF Members should review their communication preferences on psfmember.org if you would like to opt in or out of receiving emails about either the PSF Board, PC elections, or both. Here’s how:
- Login to https://psfmember.org/
- Navigate to your “Profile” page
- Click the “Name and Address” tab
- Scroll down, designate your preferences
- Click submit
If you had previously opted out of communications from the PSF through psfmember.org and would like to start receiving them, we encourage you to update them using the instructions above. If you're not sure what how your psfmember.org communication preferences are currently set, you can check via the "Name and Address" tab mentioned above, and make any adjustments as desired.
The PSF only sends a handful of election and fundraising related communications every year via psfmember.org. The PSF newsletter runs through a separate mailing list (and we heartily welcome you to sign up for our newsletter!).
Run for the Board
Who runs for the board? People who care about the Python community, who want to see it flourish and grow, and also have a few hours a month to attend regular meetings, serve on committees, participate in conversations, and promote the Python community. We're looking for candidates with a diverse range of skills and backgrounds, including leadership experience, fundraising knowledge, non-profit familiarity, and event organizing. Technical expertise, a record of collaboration, and experience speaking or teaching in the Python community are also all qualities we hope to see in Board members.
Want to learn more about being on the PSF Board? Check out the following resources to learn more about the PSF, as well as what being a part of the PSF Board entails:
- Life as Python Software Foundation Director video on YouTube
- FAQs About the PSF Board video on YouTube
- Our past few Annual Impact Reports:
You can nominate yourself or someone else. If you're nominating someone else, we'd encourage you to reach out to them first to make sure they're excited about the opportunity and give them a heads up that they'll need to submit their own nomination statement too. Nominations open on Tuesday, July 28th, 2:00 pm UTC, so you have time to talk with potential nominees, research the role, and craft a nomination statement for yourself or others. Take a look at last year’s nomination statements for reference.
Learn more and join the discussion
You are welcome to join the discussion about the PSF Board election on our forum. This year, we’ll also be hosting PSF Board Office Hours on the PSF Discord in July and August to answer questions about running for and serving on the board. Subscribe to the PSF blog or, if you’re a member, join the psf-member-announce mailing list to receive updates leading up to the election.
Python Bytes
#486 underscore-underscore-ghost-emoji
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://lwn.net/SubscriberLink/1078367/eaa511915870fdb2/?featured_on=pythonbytes">Free-threaded Python: past, present, and future</a></strong></li> <li><strong><a href="https://github.com/ahmedaljawahiry/django-admin-site-search?featured_on=pythonbytes">django-admin-site-search</a></strong></li> <li><strong><a href="https://quesma.com/blog/qwen-36-is-awesome/?featured_on=pythonbytes">Qwen 3.6 27B is the sweet spot for local development</a></strong></li> <li><strong>A large batch of PEPs are finalized</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><a href='https://www.youtube.com/watch?v=YBMyuJo9YjY' style='font-weight: bold;'data-umami-event="Livestream-Past" data-umami-event-episode="486">Watch on YouTube</a><br> <p><strong>Show Intro</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python</strong></a></li> <li>Consulting from <a href="https://sixfeetup.com/?featured_on=pythonbytes"><strong>Six Feet Up</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">Mastodon</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/mkennedy?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/mkennedy/?featured_on=pythonbytes">LinkedIn</a></li> <li>Calvin: <a href="https://sixfeetup.social/@calvin?featured_on=pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/calvinhp.com?featured_on=pythonbytes">BlueSky</a> / <a href="https://x.com/calvinhp?featured_on=pythonbytes">X</a> / <a href="https://www.linkedin.com/in/calvinhp/?featured_on=pythonbytes">LinkedIn</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">Mastodon</a> / <a href="https://bsky.app/profile/pythonbytes.fm">BlueSky</a> / <a href="https://x.com/PythonBytes?featured_on=pythonbytes">X</a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually <strong>Tuesday at 7am PT</strong>. Older video versions available there too. Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</p> <p><strong>Calvin #1: <a href="https://lwn.net/SubscriberLink/1078367/eaa511915870fdb2/?featured_on=pythonbytes">Free-threaded Python: past, present, and future</a></strong></p> <ul> <li>The GIL has prevented true multi-threaded parallelism in CPython since the beginning — multiple past attempts to remove it failed on performance grounds</li> <li>Sam Gross at Meta finally solved it; his work became PEP 703 and ships as free-threaded CPython today</li> <li>Python 3.13 was experimental with 20–40% single-threaded slowdown; 3.14 brought that to 0–10%</li> <li>Python 3.15 (October 2026) delivers a unified ABI — one extension binary works on both GIL and free-threaded builds</li> <li>Already >50% of the top PyPI binary wheels support free threading</li> <li>Wouters predicts free-threaded becomes the default between 3.16–3.20 (2027–2031), with the GIL eventually disappearing next decade</li> </ul> <p><strong>Michael #2: <a href="https://github.com/ahmedaljawahiry/django-admin-site-search?featured_on=pythonbytes">django-admin-site-search</a></strong></p> <ul> <li>via Adam Parkin</li> <li>A global/site search modal for the Django admin, by Ahmed Aljawahiry. Hit cmd+k anywhere in the admin and you get a command-palette-style search window, kind of like the one in VS Code.</li> <li>It doesn't just search one model's list page. It searches your <em>entire</em> site in one box: <ul> <li>App labels</li> <li>Model labels and field attributes</li> <li>Actual model instances (your data)</li> </ul></li> <li>Two ways to search the instances: <ul> <li><code>model_char_fields</code> (the default): runs an <code>__icontains</code> across every <code>CharField</code> (and subclasses) on the model. Zero config, works out of the box.</li> <li><code>admin_search_fields</code>: defers to each ModelAdmin's existing <code>get_search_results()</code>, so it respects the <code>search_fields</code> you've already set up.</li> </ul></li> <li>The part I like: it's permission-aware out of the box. Users only see results for the apps and models they actually have view permission on, so you're not leaking anything through search.</li> <li>Results appear as you type, with throttling/debouncing so you're not hammering the server on every keystroke, and it's full keyboard nav: cmd+k to open, up/down to move, enter to go.</li> <li>It's responsive, does dark and light mode, and it pulls Django's built-in admin CSS variables so it just matches whatever admin theme you're running.</li> <li>Under the hood it's Alpine.js, but bundled into static so there's no external CDN dependency.</li> <li>Setup is about what you'd expect: <code>pip install django-admin-site-search</code>, add it to <code>INSTALLED_APPS</code>, mix the <code>AdminSiteSearchView</code> into your AdminSite, and drop a few template includes into <code>base_site.html</code>.</li> <li>Supports Python 3.8 through 3.14 and Django 3.2 through 6.0, MIT licensed, and everything is overridable if you want to skip certain models, add <code>TextField</code> matching, etc.</li> </ul> <p><strong>Calvin #3: <a href="https://quesma.com/blog/qwen-36-is-awesome/?featured_on=pythonbytes">Qwen 3.6 27B is the sweet spot for local development</a></strong></p> <ul> <li>Qwen 3.6 27B is being called the first local model that genuinely competes as a general-purpose intelligence — benchmarks put it at roughly mid-2025 frontier level (comparable to GPT-5 / Claude Sonnet 4.5)</li> <li>Runs locally via llama.cpp; on an M5 MacBook Max with 8-bit quantization + multi-token prediction, it hits ~32 tokens/sec using ~42GB RAM</li> <li>4-bit quantization gets it under 18GB, runnable on 32GB devices; Nvidia RTX cards run it even faster</li> <li>The dense 27B is recommended over the faster MoE 35B A3B — author prefers higher quality output over raw speed</li> <li>Privacy and reliability are the pitch: fine-tunable, can't be taken down, suitable for sensitive/proprietary data</li> <li>Author sees this as a stepping stone — frontier open-weight models like GLM 5.2 are now locally runnable with company-grade hardware, and smarter-still local models are coming</li> </ul> <p><strong>Michael #4: A large batch of PEPs are finalized</strong></p> <ul> <li>A bunch of PEPs went from accepted to final. <ul> <li>668, 687, 691, 699, 701, 703, 728, 770, 773, 829</li> </ul></li> <li>But this wasn’t them making their way into CPython. It’s an admin sorta thing. (Thanks PyCoders)</li> <li>See <a href="https://github.com/python/peps/commit/f866e77409305866038471574f075cd8d83eee9e?featured_on=pythonbytes">the commit</a>.</li> </ul> <p><strong>Extras</strong></p> <p>Calvin:</p> <ul> <li>More fun bling for your terminal this time - https://charm.land/</li> </ul> <p>Michael:</p> <ul> <li>Follow up from pls, <a href="https://mkennedy.codes/posts/what-the-pls/?featured_on=pythonbytes">What the pls?</a> Thanks Pito.</li> </ul> <p><strong>Joke: <a href="https://tomhayes.github.io/BEMoji/?featured_on=pythonbytes">BEMoji</a></strong></p> <ul> <li>A production-grade utility and component framework built entirely on emoji class names</li> <li>via Jeff Triplett</li> </ul>
ListenData
How to Automate Cloudflare Usage Monitoring
If you are using Cloudflare products like Workers, R2, or D1, you have probably checked the dashboard more times than you would like.
It works, but it is manual, repetitive, and not very scalable. What if you could just run a script and instantly see your usage?That is exactly what we will do here.
Why automate Cloudflare usage tracking?
Cloudflare gives you powerful analytics, but:
- You have to open the dashboard every time
- You cannot easily combine data across services
- Tracking trends daily or hourly becomes tedious
With the following python script, you can solve this by automating:
- Workers usage: requests, errors, CPU time
- R2 usage: reads, writes, total requests per bucket
- D1 usage: queries, rows read and written per database
Steps to Automate Tracking for Workers, R2 & D1
1. Install dependency
pip install requests
2. Get your API credentials
From your Cloudflare dashboard:
- Create an API token with Account Analytics Read
- Copy your CF_API_TOKEN and CF_ACCOUNT_ID
3. Run Python Code
You can either set environment variables for API token and account ID like shown below or paste them directly inside the script if you prefer.
export CF_API_TOKEN="your_token_here"
export CF_ACCOUNT_ID="your_account_id"
import os
import requests
from datetime import datetime, timezone, timedelta
HAS_TABULATE = False
# ─── CONFIG ──────────────────────────────────────────────────────────────────
CF_API_TOKEN = os.getenv("CF_API_TOKEN", "xxxxxxxxxxxxxxxxxxx")
CF_ACCOUNT_ID = os.getenv("CF_ACCOUNT_ID", "xxxxxxxxxxxxxxxxxxx")
GRAPHQL_URL = "https://api.cloudflare.com/client/v4/graphql"
REST_BASE = "https://api.cloudflare.com/client/v4"
HEADERS = {
"Authorization": f"Bearer {CF_API_TOKEN}",
"Content-Type": "application/json",
}
# ─── HELPERS ─────────────────────────────────────────────────────────────────
def iso(dt: datetime) -> str:
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
def fmt_bytes(b: int) -> str:
for unit in ("B", "KB", "MB", "GB", "TB"):
if abs(b) 1024:
return f"{b:.1f} {unit}"
b /= 1024
return f"{b:.1f} PB"
def print_table(title: str, rows: list[dict], keys: list[str] | None = None):
print(f"\n{'─'*60}")
print(f" {title}")
print(f"{'─'*60}")
if not rows:
print(" (no data)")
return
keys = keys or list(rows[0].keys())
header = " " + " | ".join(f"{k:20}" for k in keys)
print(header)
print(" " + "-" * (len(header) - 2))
for row in rows:
print(" " + " | ".join(f"{str(row.get(k,'')):20}" for k in keys))
def gql(query: str, variables: dict = None) -> dict:
payload = {"query": query}
if variables:
payload["variables"] = variables
r = requests.post(GRAPHQL_URL, headers=HEADERS, json=payload, timeout=30)
r.raise_for_status()
data = r.json()
if errors := data.get("errors"):
raise RuntimeError(f"GraphQL errors: {errors}")
return data["data"]
def rest_get(path: str, params: dict = None) -> dict:
r = requests.get(f"{REST_BASE}{path}", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
return r.json()
# ─── 3. WORKERS USAGE (GraphQL) ───────────────────────────────────────────────
def get_workers_usage(days: int = 30) -> list[str]:
print(f"\nWORKERS USAGE (last {days} days)")
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
alerts: list[str] = []
query = """
query WorkersUsage($accountId: String!, $start: String!, $end: String!) {
viewer {
accounts(filter: {accountTag: $accountId}) {
workersInvocationsAdaptive(
limit: 10000
filter: {datetime_geq: $start, datetime_leq: $end}
) {
sum {
requests
errors
subrequests
}
quantiles {
cpuTimeP50
cpuTimeP99
}
dimensions {
scriptName
}
}
}
}
}
"""
try:
data = gql(query, {"accountId": CF_ACCOUNT_ID,
"start": iso(start), "end": iso(now)})
records = (data["viewer"]["accounts"][0]
.get("workersInvocationsAdaptive", []))
# Aggregate by scriptName: Cloudflare may return >1 row per script
# due to implicit time-bucket dimensions in adaptive sampling.
# Sum requests/errors; take max CPU across rows (CPU percentiles
# don't sum meaningfully, so report the highest observed).
aggregated: dict[str, dict] = {}
for rec in records:
s = rec.get("sum", {})
q = rec.get("quantiles", {})
script_name = rec["dimensions"].get("scriptName", "(unnamed)")
if script_name not in aggregated:
aggregated[script_name] = {
"requests": 0,
"errors": 0,
"cpu_p50": 0,
"cpu_p99": 0,
}
aggregated[script_name]["requests"] += s.get("requests", 0) or 0
aggregated[script_name]["errors"] += s.get("errors", 0) or 0
# Track maximum CPU across rows for safety
row_p50 = (q.get("cpuTimeP50") or 0) / 1_000
row_p99 = (q.get("cpuTimeP99") or 0) / 1_000
if row_p50 > aggregated[script_name]["cpu_p50"]:
aggregated[script_name]["cpu_p50"] = row_p50
if row_p99 > aggregated[script_name]["cpu_p99"]:
aggregated[script_name]["cpu_p99"] = row_p99
rows = []
for script_name, totals in aggregated.items():
rows.append({
"Script": script_name,
"Requests": f"{totals['requests']:,}",
"Errors": f"{totals['errors']:,}",
"CPU ms": f"{totals['cpu_p50']:.1f}",
"CPU P99 ms": f"{totals['cpu_p99']:.1f}",
})
if totals["requests"] > WORKERS_REQUEST_THRESHOLD:
alerts.append(
f"Workers '{script_name}' requests {totals['requests']:,} > {WORKERS_REQUEST_THRESHOLD:,}"
)
if totals["cpu_p50"] > WORKERS_CPU_MS_THRESHOLD:
alerts.append(
f"Workers '{script_name}' CPU ms {totals['cpu_p50']:.1f} > {WORKERS_CPU_MS_THRESHOLD:.1f}"
)
rows.sort(key=lambda r: int(r["Requests"].replace(",", "")), reverse=True)
print_table("Workers Invocations by Script",
rows, ["Script", "Requests", "Errors", "CPU ms", "CPU P99 ms"])
return alerts
except Exception as e:
print(f" Workers data unavailable: {e}")
return []
# ─── 4. R2 STORAGE & OPERATIONS (GraphQL) ────────────────────────────────────
def get_r2_usage(days: int = 30):
print(f"\nR2 STORAGE & OPERATIONS (last {days} days)")
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
query = """
query R2Usage($accountId: String!, $start: String!, $end: String!) {
viewer {
accounts(filter: {accountTag: $accountId}) {
r2OperationsAdaptiveGroups(
limit: 10000
filter: {datetime_geq: $start, datetime_leq: $end}
) {
sum {
requests
}
dimensions {
bucketName
actionType
}
}
}
}
}
"""
try:
data = gql(query, {"accountId": CF_ACCOUNT_ID,
"start": iso(start), "end": iso(now)})
records = (data["viewer"]["accounts"][0]
.get("r2OperationsAdaptiveGroups", []))
# Aggregate per bucket
buckets: dict[str, dict] = {}
for rec in records:
name = rec["dimensions"].get("bucketName", "(unknown)")
action = (rec["dimensions"].get("actionType", "") or "").lower()
s = rec.get("sum", {})
if name not in buckets:
buckets[name] = {"requests": 0, "reads": 0, "writes": 0}
buckets[name]["requests"] += s.get("requests", 0)
# Classify operations into reads vs writes based on common R2 action types
if any(k in action for k in ("get", "read", "head", "list")):
buckets[name]["reads"] += s.get("requests", 0)
if any(k in action for k in ("put", "post", "write", "delete")):
buckets[name]["writes"] += s.get("requests", 0)
rows = [{"Bucket": k,
"Requests": f"{v['requests']:,}",
"Reads": f"{v['reads']:,}",
"Writes": f"{v['writes']:,}"}
for k, v in sorted(buckets.items(),
key=lambda x: x[1]["requests"], reverse=True)]
print_table("R2 Usage by Bucket", rows, ["Bucket", "Requests", "Reads", "Writes"])
except Exception as e:
print(f" R2 data unavailable: {e}")
# ─── 5. D1 DATABASE USAGE (GraphQL) ──────────────────────────────────────────
def get_d1_usage(days: int = 30):
print(f"\nD1 DATABASE USAGE (last {days} days)")
now = datetime.now(timezone.utc)
start = now - timedelta(days=days)
# 1. Fetch database names via REST API to map IDs to friendly names
db_names = {}
try:
res = rest_get(f"/accounts/{CF_ACCOUNT_ID}/d1/database")
db_names = {db["uuid"]: db["name"] for db in res.get("result", []) if "uuid" in db}
except Exception:
pass
# 2. Query GraphQL with datetime filters to match dashboard's precise windows
query = """
query D1Usage($accountId: String!, $start: String!, $end: String!) {
viewer {
accounts(filter: {accountTag: $accountId}) {
d1AnalyticsAdaptiveGroups(
limit: 1000
filter: {datetime_geq: $start, datetime_leq: $end}
) {
sum {
readQueries
writeQueries
rowsRead
rowsWritten
}
dimensions {
databaseId
}
}
}
}
}
"""
try:
data = gql(query, {"accountId": CF_ACCOUNT_ID,
"start": iso(start), "end": iso(now)})
records = (data["viewer"]["accounts"][0]
.get("d1AnalyticsAdaptiveGroups", []))
db_totals: dict[str, dict] = {}
for rec in records:
db_id = rec["dimensions"].get("databaseId", "?")
s = rec.get("sum", {})
if db_id not in db_totals:
db_totals[db_id] = {"reads": 0, "writes": 0, "rowsRead": 0, "rowsWritten": 0}
db_totals[db_id]["reads"] += (s.get("readQueries") or 0)
db_totals[db_id]["writes"] += (s.get("writeQueries") or 0)
db_totals[db_id]["rowsRead"] += (s.get("rowsRead") or 0)
db_totals[db_id]["rowsWritten"] += (s.get("rowsWritten") or 0)
rows = [{
"Database": db_names.get(db_id, db_id[:18]),
"Read Rows": f"{totals['rowsRead']:,}",
"Write Rows": f"{totals['rowsWritten']:,}",
"Queries": f"{totals['reads'] + totals['writes']:,}",
} for db_id, totals in sorted(db_totals.items(),
key=lambda item: item[1]["rowsRead"] + item[1]["rowsWritten"],
reverse=True)]
print_table("D1 Usage by Database (Billable Metrics)",
rows, ["Database", "Read Rows", "Write Rows", "Queries"])
except Exception as e:
print(f" D1 data unavailable: {e}")
# ─── 7. SPENDING SUMMARY ─────────────────────────────────────────────────────
# ─── MAIN ─────────────────────────────────────────────────────────────────────
def main():
print("=" * 60)
print(" Cloudflare Billing & Usage Tracker")
print(f" Account: {CF_ACCOUNT_ID[:8]}…")
print(f" Run at: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
print("=" * 60)
get_workers_usage(days=1)
get_r2_usage(days=1)
get_d1_usage(days=1)
if __name__ == "__main__":
main()
After running the code above, you will see output like this:
============================================================
Cloudflare Billing & Usage Tracker
Account: exxxxxxxxxx…
Run at: 2026-05-05 13:35 UTC
============================================================
WORKERS USAGE (last 1 days)
────────────────────────────────────────────────────────────
Workers Invocations by Script
────────────────────────────────────────────────────────────
Script | Requests | Errors | CPU ms | CPU P99 ms
----------------------------------------------------------------------------------------------------------------
t-analysis | 36,780 | 0 | 17.1 | 137.8
notify---purge | 46 | 0 | 0.6 | 11.7
d1-example | 35 | 0 | 2.0 | 11.9
R2 STORAGE & OPERATIONS (last 1 days)
────────────────────────────────────────────────────────────
R2 Usage by Bucket
────────────────────────────────────────────────────────────
Bucket | Requests | Reads | Writes
-----------------------------------------------------------------------------------------
stocks | 38,746 | 29,987 | 8,759
tradedata | 6,875 | 6,875 | 0
etfs | 1,612 | 1,127 | 485
logos | 620 | 620 | 0
| 8 | 8 | 0
D1 DATABASE USAGE (last 1 days)
────────────────────────────────────────────────────────────
D1 Usage by Database (Billable Metrics)
────────────────────────────────────────────────────────────
Database | Read Rows | Write Rows | Queries
-----------------------------------------------------------------------------------------
cxxxx-xxx-xxxxxxxx | 28,166,287 | 3,715 | 8,372
Bob Belderbos
Ask the Canon: Semantic Search Without a Vector Database
I built out askthecanon.com this weekend, a semantic search over 100 public-domain books (from the Gutenberg project). You ask a question in plain language and get the passages that mean that, cited by author, title, and chapter. I wanted a local, no-generative-AI solution: a retrieval engine using Hugging Face embeddings and NumPy that returns original passages, no full vector database (yet), no API calls at query time.
Why Ask the Canon?
I am already finding timeless wisdom using it myself (some of the best apps come from "scratching your own itch"), and I hope it offers a breath of fresh air in a world that seems to be dominated by AI-generated content and quick summaries.
My itch was wanting to read the originals, not an AI-generated summary, but also recognizing I don't have time and focus to read through a whole work (although it's still my aim, I see deep value in it). What if we can meet somewhere in the middle?
Most of what we wrestle with is not new: fear, ambition, grief, how to deal with people who wrong us. A chatbot will distill what the canon says about any of it in seconds, in one smooth, agreeable, slightly forgettable voice. Sometimes that's enough. Often I'd rather read the actual sentence Marcus Aurelius or Francis Bacon wrote and sit with it.
Thoreau, in Walden, on what real reading asks of you:
"Most men have learned to read to serve a paltry convenience ... but of reading as a noble intellectual exercise they know little or nothing; yet this only is reading, in a high sense, not that which lulls us as a luxury and suffers the nobler faculties to sleep the while, but what we have to stand on tip-toe to read and devote our most alert and wakeful hours to."
Ask the Canon does only that: it points a plain-language question at a hand-picked shelf of public-domain books and returns the real passages that answer it, cited down to the chapter. It never writes a word of its own, so nothing is invented and nothing is misattributed.

A result, rendered by the app's own "share as image" feature. Thoreau made my argument in 1854.
This is the first of three posts on how it's built. This one is the engine: how you go from a folder of messy text files to ranked, cited answers without reaching for the heavy infrastructure everyone assumes you need.
The default is over-engineered
Reach for "semantic search" and the stock answer is a vector database (Pinecone, Weaviate, pgvector) plus an embeddings API you call on every query.
That's the right shape at a billion vectors. At personal scale, tens of thousands of passages, it buys you operational weight and a network round-trip you don't need.
The whole corpus here is 79,292 passages across 100 books. As a float32 matrix at 768 dimensions that's about 240 MB, small enough to load once and keep resident. Once it's in RAM, "find the most similar passage" is a matrix multiply, and np.argsort over the result. That's the entire search:
def embed(texts: list[str]) -> np.ndarray:
return _model().encode(texts, normalize_embeddings=True)
def retrieve(query: str, vectors: np.ndarray, k: int = 5) -> list[tuple[int, float]]:
scores = vectors @ embed([query])[0]
top = np.argsort(scores)[::-1][:k]
return [(int(i), float(scores[i])) for i in top]
Because the vectors are L2-normalized at embed time (normalize_embeddings=True), the dot product vectors @ query is cosine similarity. No similarity function to import, no index to tune. One @.
(I later refactored that bare multiply into a _scores() helper so the index can ship as float16, halving its memory on a small box. The math is identical; how the helper keeps a float16 matmul fast is a Part 2 detail.)
Embed once, cache to disk
The trick that makes this cheap: you embed the corpus exactly once. The model never runs at query time except on the single short query string. I run a local all-mpnet-base-v2 model, so there's no API key and nothing leaves the machine.
Indexing a book writes the vectors straight to a .npy file next to the source:
def build_index(book_id: int, text: str) -> tuple[list[Chunk], np.ndarray]:
chunks = chunk_text(text)
vectors = embed([c.text for c in chunks])
np.save(BOOKS_DIR / f"{book_id}.npy", vectors)
return chunks, vectors
At startup, the per-book matrices stack into one library matrix with np.vstack, and that's what every query multiplies against.
Embedding is the only expensive step, and it happens offline, on my laptop, never on the server. This separation also makes the deploy boring: build the .npy files locally, rsync them to the droplet.
The server never loads the model to build anything; it only embeds the incoming query. (The model loading itself is lazy and gated on offline env vars so there's no hub round-trip, but that's a Part 2 detail.)
Chunks carry their own citations
A vector store usually means a second store for metadata: which book, which chapter, where in the text. I don't have one. The citation rides along with the chunk.
When I split a book, I track Gutenberg's CHAPTER / BOOK / CANTO headings as I go and stamp each chunk with the section it fell in:
class Chunk(NamedTuple):
label: str # e.g. "BOOK XI — Chapter IX"
text: str
So a result isn't a naked paragraph. It's Marcus Aurelius · Meditations — BOOK IV, reconstructed from data that lived in the chunk all along.
The whole "database" is four kinds of file, no server:
books/<id>.npy: the embeddings for one bookbooks/<id>.chunks.json: the passages and their chapter labelsbooks/<id>.meta.json: the title, author, and a short book summarylibrary.txt: the list of Gutenberg IDs I grow by hand (now committed to the repo, could split off as "config" later)
To add or remove books, I update library.txt and run sync to rebuild the index which I then rsync to the server. No database, no migrations, no schema, no API calls.
The bug hiding in the chunk size
Chunk size is the one knob that decides whether any of this works, and my first cut got it wrong in a "silent error" way.
all-mpnet-base-v2 reads at most 384 tokens, roughly 290 words, per input. Anything longer is truncated before a single number is computed.
My first chunk_text targeted 600-word chunks, and it checked the size after appending each paragraph, so 600 was a floor it always overshot. A 599-word chunk plus one more paragraph landed well past 1,000 words.
So the model embedded the opening third of each passage and silently dropped the rest. The text I stored and cited was the whole passage, but the vector ranking it represented only the first few hundred words. Search was answering on text it had never read, and a long passage's real subject often sat in the part that got cut.
Nothing errored. The results looked on point, but they were quietly wrong: ranking was decided by the opening of each passage and ignored the rest. The cited passages were often long, and search was not matching the later paragraphs that actually could have better answered the query.
The fix was two small changes. Drop the target to 250 words, a safe buffer under the token limit, and flush a chunk before a paragraph pushes it over, so target_words is a ceiling instead of a floor:
TARGET_WORDS = 250 # ~330-350 tokens, below mpnet's 384 max
para_words = len(para.split())
if current and words + para_words > target_words:
chunks.append(Chunk(chunk_label, "\n\n".join(current)))
current = current[-overlap:] if overlap else []
words = sum(len(p.split()) for p in current)
Now every chunk is embedded in full. Matches got sharper and the cited passages are short enough to read at a glance. Smaller chunks also nearly tripled the corpus, from 29,435 passages to 79,292, which is why the matrix grew while the search improved.
One follow-on: tighter chunks shifted the score distribution (spotted doing some end-to-end testing with Claude), so I raised the noise threshold from 0.32 to 0.34 to keep false positives out.
When a vector database stops being overkill
A thoughtful skeptic would push back: this doesn't scale. Correct, and that's the point. A linear scan is O(n) per query. At 80k passages it's a few milliseconds; at 30 million it's not. The moment you outgrow a single machine's RAM, or need filtered queries, multi-tenant isolation, or sub-millisecond latency at scale, we'd go with a real vector database.
But as detailed in Build the Simplest Thing That Works I prefer to get something working fast and validate it first putting it in front of real users.
Two lines of NumPy sit at the root of this engine. Everything else, the cards, the PDF export, the off-domain rejection, is built on top of that.
Using Claude was awesome, both in terms of getting the vectorization off the ground and it matching a classic vibe design so well. But 100-plus commits in, I kept having to step in with my experience and judgment to make the right call and tune things.
This judgment call is the same one I keep coming back to: match the tool to the actual size of the problem, not the size you imagine. It's the kind of engineering judgment AI doesn't change. AI is an accelerator, not a compass, and it still needs you to point it.
HoloViz
Introducing Panel Live Server: An MCP Server for Instant Python Visualization Rendering
June 29, 2026
Talk Python Blog
Portuguese subtitles available for all courses
Over the past couple of months, we announced support for multi-lingual subtitles on our courses, starting with German and then Spanish. Now we are ready to release our third language, Portuguese!

All 283 hours of courses have complete Portuguese subtitles. Just choose your language, set the subtitle size and location and you have high-quality Portuguese subtitles to accompany your learning.
Your next course
What’s next? Well, either drop into your account page and continue with an existing course you’re studying or browse our catalog of courses to find your next one.
Django Weblog
DSF member of the month - Salim Nuru
For June 2026, we welcome Salim Nuru as our DSF member of the month! ⭐
Salim was a Djangonaut Space participant in the first session. He has been an organizer of the DjangoCon Africa conference. He is currently the chair of the DjangoCon US website team. He is a DSF member since October 2024. He is looking for new opportunities!
You can learn more about Salim by visiting Salim's blog and his GitHub Profile.
Let’s spend some time getting to know Salim better!
Can you tell us a little about yourself (hobbies, education, etc)?
I'm Salim, I’m from Addis, Ababa Ethiopia. I'm a software engineer by day and a security researcher by night. And for fun, I like chess, video games, and books.
I already have an idea, but where does your nickname "theShinigami" come from?
I LOVE anime, and the very first anime I watched was Death Note. There's this character called Ryuk, who is a "shinigami," and when I created my GitHub account, it was a time when I was really into anime (which I still am). That's why it stuck as my GitHub username.
How did you start using Django?
I started using Django during my college years. I was doing freelance work and mostly using .NET and JavaScript, when I got a huge project that needed to be built with Django. I didn't want to pass up the opportunity. At that time, I had heard about Django but never gotten to use it, so I had a week to prepare and spent every minute of it learning Django. I liked how easy it was to learn.
What other frameworks do you know, and is there anything you would like to have in Django if you had magical powers?
From Python based frameworks, I use Flask, FastAPI, etc. And I would like Django to support REST APIs out of the box.
What projects are you working on now?
I have a couple of projects I'm working on currently, and the one I'm proud of, and actively working on, is a platform that scans an Android app and gives security suggestions. It also has an AI that can do a deep scan and suggest a proof of concept if any vulnerabilities are found in the app. This is my first project that involves AI and running my own local LLM for security.
Which Django libraries are your favorite (core or 3rd party)?
There are a lot of great libraries, but the Django Debug Toolbar has a special place in my heart. Also Django Rest Framework (DRF), which I use for most of the projects I work on.
What are the top three things in Django that you like?
Community, security by default, and finally the admin panel.
I know you have a lot of knowledge in cybersecurity. How do you find the security in Django? Have you ever thought about being part of the security team, by any chance?
I'm still learning, and in Django I like how it applies security by default, which is a good thing. For now, I'm replicating the CVEs found in Django, just trying to understand them and find my next CVE in Django 🤞, and hopefully it would be great to work with the Django security team 🙂
You have been an organizer of DjangoCon Africa, thank you for organizing it. Organizers always do a lot of work that people can't see. How did you start? What are the things that surprised you or that you didn't expect as an organizer?
Organizing is a team effort. I like the saying “there is no "I" in DjangoCon US” 🙂
I attended my very first DjangoCon at the very first DjangoCon Africa, as a speaker, and I really enjoyed it. I liked how the community was really welcoming and friendly, and right there and then I decided that this was going to be my community and that I had to do my part.
So I joined the organizing team for the next DjangoCon Africa, and after organizing it, I was really surprised by how many people from the community joined the event, and how far they had come just to attend the conference.
You are now the chair of the DjangoCon US website, that's amazing! What does it mean exactly? How has your experience been so far?
Well, if people want to know anything about the conference, they're going to be checking out the website, and as the chair, I should be able to make that experience great. As for my own experience, I think it's really great and a step up in role. I'm learning a lot, and I'm very happy that I'm able to do it.
You now have some experience in organizing big events. Do you have any recommendations for people who would potentially be interested in contributing to or organizing this type of event?
Don't wait to feel ready, because that feeling rarely comes. Take on a small role first, lean on the people around you, and keep everyone in the loop. So to anyone considering it, I'd say jump in, the experience is well worth it.
You have been part of the Djangonaut Space program as a djangonaut (participant), and you are now involved in the community in many ways. How do you reflect on your evolution since your participation in the program? Any advice for potential new contributors or people who would like to give it a try?
When I joined Djangonaut Space as a participant, I would say that was the highlight of my year, because I always wanted to contribute to open source, especially to Django and Python projects, but I always hesitated to do it. The program really helped me, from picking my first issue to creating the PR (a big shoutout to Fabian, lead maintainer of Django CMS) it was really amazing. And for any new contributors, if you're planning to join Djangonaut Space, it's not just going to help you with your open source contribution, you're going to be joining a community.
Do you remember your first contribution to Django and to open source?
My very first contribution was to a tool for binary instrumentation. I was doing some reverse engineering, but there was a bug that made it difficult, so I had to understand and fix the bug and then create a PR. I remember getting good feedback and having a good interaction via Discord, and the PR was finally merged 🙂. In Django, it was Django CMS, and the issue I worked on was the missing X-Frame option from the Advanced form (#7981). It was a great first issue, and it taught me a lot.
Is there anything else you’d like to say?
I'd just like to thank the Django community for being so welcoming to newcomers, and I'm looking forward to making new contributions (especially in security) 🙂.
Thank you for doing the interview, Salim !
Mike Driscoll
Python eBook and Course Summer Sale
It’s officially summer, and I am bringing you some HOT Python deals today! Get 33% off almost all my books and courses on Gumroad today using the following H5N5F7K
You can start learning the basics of Python with Python 101, or get more targeted learning with my book, Python Logging. If you want to create a user interface, then you might enjoy Creating TUI Applications with Textual and Python.
I have over a DOZEN Python books to choose from! Check them out today: https://driscollis.gumroad.com/

Plus even more that aren’t pictured here!
The post Python eBook and Course Summer Sale appeared first on Mouse Vs Python.
Seth Michael Larson
United Nations Open Source Week 2026
I was among the delegation of “open source experts” invited to the UN Open Source Week 2026 in New York City by the Sovereign Tech Agency. Thank you to the Sovereign Tech Agency for inviting and supporting my stay and travel for the event. Thanks to Alpha-Omega for sponsoring my position at the Python Software Foundation.
UN Open Source Week is a week-long event with a different focus for each day. In order, the focuses were: Maintain-a-thon (UN Tech Over), Open Source × AI, Digital Public Infrastructure Day, OSPOs for Good, and Community Day. The event is structured into a series of presentations, panels, parallel sessions, interactive break-outs that start in the morning and carry on through into the evening at local partnered events.
Themes
After speaking with many folks and attending a week of sessions, there were themes that carried through the entirety of the event:
- Power, resources, and talent around LLM technologies are currently concentrated into the control and geographical borders of few. Many countries and organizations are struggling with access to these technologies. A substantial portion of the answer is Open Source software and models of various definitions, but compute and talent are more difficult problems. Better efficiencies, distributed training and inference, sharing compute resources, and programs to transfer skills to other communities were among discussed solutions.
- LLMs are changing many of the social norms for Open Source participation which disrupts processes that have been in place for decades. Upstream and downstream processes will need to be developed to handle these changes.
- There were acknowledgements that many people were on edge or scared, that the metaphorical “tomorrow” was less clear to see than it was a few years ago.
There was also plenty of hope in the sessions, too. Similar to last year, I left feeling that Open Source was a critical component for overcoming the challenges ahead and that organizations around the world knew this acutely.
Multiple speakers asked those involved with Open Source projects to see how their projects aligned with the 17 Sustainable Development Goals, including quality education, clean energy, industry and infrastructure, and many more. Having done this exercise, I highly recommend others do so, too.
Maintain-a-thon 2.0
The Sovereign Tech Agency was the partner hosting the second “Maintain-a-thon” as a part of the first day of UN Open Source Week. This year the day was split into two parallel tracks: “Technical Maintenance” and “Capacity & Stewardship”.
Giving context for LLMs and vulnerability reports for the Python programming language.
Mirko Swillus and I hosted a session in the Technical Maintenance track titled “The Vulnerability Flood: Open Source Security in the Age of LLMs”. The session would discuss how LLMs were affecting vulnerability handling and security teams and how we might better plan for potential futures. We began the session by setting context around how LLMs were already changing security, such as:
- publicly available models are able to discover vulnerabilities.
- LLMs can detect whether patches are security-relevant, regardless of whether an advisory is published (embargoes are less useful).
- time-to-exploit for vulnerabilities is decreasing.
- “AI slop” vulnerability reports and how in recent months quality has improved.
- how projects like the Python programming language are thinking today about “steering” these LLM-assisted contributions in a positive direction through security policies and threat models.
The session proceeded into an interactive exercise to draw potential topics for deeper small-group discussions from participants using sticky notes. The three topic-clusters ended up being “People”, “Process”, and unsurprisingly “AI”.
The “People” group discussed offering mental health programs for Open Source maintainers to better handle stress, burnout, and succession planning and highlighted the difficulties in defining what it even means to be a maintainer in terms of a “job description”.
The “AI” group discussed the critical junction for handling unmaintained software in a world of agents and faster time-to-exploits, focusing on the question: “Fix or rewrite?”. Clearly rewrites should be a last-resort and are fraught with challenges, such as introducing more bugs and security issues due to a large volume of new code. The group highlighted challenges and potential solutions around LLM use for Open Source projects in handling the flood of security reports.
The “Process” group discussed the weakening value of secrecy when it comes to vulnerability reports discovered using LLMs. Historically secrecy was kept to protect users, but if public models are able to find issues then who does this aspect of coordinated-vulnerability disclosure actually help? (Attackers). The Linux kernel is already experimenting with having less secrecy involved in vulnerability handling.
Notes from the AI group Small python dep that keeps getting reports, is there a point where it should get rebuilt (with AI), rewrite it in rust for memory safety, identify the jenga block Is it so bad that it needs a complete overhaul, how is the architecture? (pre-ai assumption) Programme Bench (ai benchmark, does an ai rewrite something reliably) Depends on the size of the project, often 90% isn’t used What happens when there are no maintainers? Is the project well maintained enough? Does it have governance model that lets ai take over? We’ve seen ai rewrites that also change licenses at the same time When you make something from the ground up you learn it better, if you take something over, learning all the edge cases can be more work that rewriting from scratch. In any case, whoever takes over, new maintainers have to learn the edge cases and understanding. You could feed all git history into a model to ensure it learns all the failures and fixes over time. If we believe that AI is good enough to do an AI rewrite properly Rewrites are hard, introduces more problems that were there anymore Project moved in a direction from paid hosted models to free self hosted models, dealing with resource constraints. Easier to pitch AI projects from an ethics POV if it’s using self-hosted “open source” ai models. Feels better and better perceived about using “open source” models, but worried about how it scales. Choosing to engage with AI can cross red lines with committers and users that causes lots of issues. Pitching OSS projects as “AI powered” often upsets users. Getting users past “it has AI in the name” Resource constraints can make it needed to use AI because there are no people and no funding, locally run ai powered approaches come across more reasonably. Humanitarian sectors are slow to adopt this technology and generally slower moving in general. Airflow - Big open source projects - big number of incoming requests, hard to tell if incoming is human or agents. Processing issue trackers using AIs, ensuring things are triage and keeping them up to date. More process == more overhead. Having experienced developers looking at the triage process rather than the initial untrusted issues. Reducing the friction of adoption of AI - generation vs assistive Calling “ai” things - Machine learning to be clear if it’s generative llms. Can fix faster be useful for accessibility of using software where there user previously never had any software experience
Notes from the People group Mental Health Programme for FOSS Maintainers How do we create awareness, so that people sign up for this? Outspoken and explicit “Job Descriptions” for single maintainers so that is easier to understand which functions can be trained and educated Clear path for becoming a mentor Sunsetting Projects Support structures for sunsetting projects & find replacements Ment or Students Learn from best practices (french DINUM, 2-3 years)
Open Source × AI Day Reception
At the reception for Open Source × AI Day there was an additional panel session focusing on LLMs and security. One of the most interesting talking points of this panel was the restrictions being placed on “Frontier” models with cybersecurity capabilities. Earlier in the week, GLM-5.2, an open weights model had been released and folks already had begun testing the model’s cybersecurity capabilities and found them to be already quite capable.
The panel noted how open weights models appear to be fast-following “Frontier” capabilities with a delay between 6-12 months, implying that we may not need to wait long for an open weights model with Mythos-like capabilities to become available. This is based on speculation, but there are many implications for this are... interesting, to put it lightly. :)
Thanks for reading ♥ I would love to hear your thoughts! Contact me via Mastodon, Bluesky, or email. Browse the blog archive. Check out my blogroll.
June 28, 2026
"Michael Kennedy's Thoughts on Technology"
What the pls?
uv tool, pipx, and uvx will work the way you’d expect. The catch: none of this is live yet. It ships with v7, which is still in beta, so a plain uv tool install pls today still pulls the abandoned v6, and the metadata issues (the 404 homepage and broken links) won’t be cleaned up until v7 is released. Want the fix right now? Install the beta directly from the pls 7.0.0b1 release on PyPI. A big thank you to Dhruv for jumping on this so quickly. See the full thread in issue #153.
tl;dr; The pls package on PyPI is an abandoned Python version (last released as v6.0.0 in 2023). The actively-developed pls was rewritten in Rust and now lives at pls-rs/pls. If you installed it with uv tool install pls or pipx install pls, you have the wrong one. Uninstall it and install the Rust build instead (e.g. brew install pls-rs/pls/pls).
You may have heard me sing the praises of pls. I really love the icons and colors to disambiguate files and provide more information about them, the developer workflow, and more. Here’s an example in the Warp terminal for my jinja-partials package.

But installing and managing this package is weird and kinda deceiving to say the least. pls was originally pure Python (up to v6.0.0 in 2023). It’s listed on PyPI here. So it looks like that is just the latest, right? After all, if it was rewritten in Rust, it can still be installed via PyPI and in particular, via uv tool install pls.
But no.
There are a few funky things about the PyPI listing that give it away:
- The homepage 404s. The homepage link on the PyPI page goes nowhere. A bit sus.
- The GitHub repo is a silent redirect. The repo seems alive, but the link https://github.com/dhruvkb/pls silently redirects to https://github.com/pls-rs/pls. Clicking the link in PyPI seems to reference the Rust/latest version. But this happens only because GitHub does the redirect.
How do I install the correct, Rust-based pls?
To get the actively-developed version, don’t let PyPI fool you. There are two steps:
- Uninstall the Python version if you installed it via Python:
uv tool uninstall pls(orpipx uninstall pls). - Then install the Rust version directly. I use Homebrew, so it’s
brew install pls-rs/pls/pls. See their getting started page for the option that works best for you.
Should you switch from the PyPI version of pls?
Yes - if you installed it with a Python tool, switch to the Rust version. Normally, I’d just chalk this up to standard package / open source drift and carry on with my life. But I’ve recommended pls to enough people that I feel I should call a bit of attention here. So if you’re using pls and you used Python tools to install it, like uv, uninstall that version and jump over to the Rust-based one.
Python Insider
Packaging Council Inaugural Election Dates
A new Python Packaging Council (PPC) is being established, with their election of the inaugural PPC will be held in parallel to the 2026 PSF Board election.
June 27, 2026
Go Deh
Sparse Ranges
Implementing sparse_range: From a Python Discuss Idea to a Sieve Stress Test
I found an interesting thread over on the Python Discuss forum titled "Possibility to exclude ranges from range". The initial discussion revolved around a common (?), developer need of : how to cleanly skip specific blocks of ints in a range of ints without writing clunky, nested if/continue logic, and without doing something memory-heavy like casting everything into a massive set.
The consensus was that Python's native range is beautiful because it’s a lightweight, memory-efficient arithmetic engine. It doesn't store numbers in RAM; it just calculates them on the fly. But the moment you need to punch holes in it—say, "give me all numbers from 1 to 1000, except for multiples of 5, and except for the block from 200 to 300"—you lose that structural elegance.
This inspired me to build a clean, production-grade abstraction to solve this kind of thng: sparse_range.
The Architectural Concept
The core idea began with a simple mathematical definition: a Sparse range instance is created by a set of (python) ranges S_in that hold possible output values and a set of ranges S_ex that exclude possible output values. A Sparse_range instance can be called with a start, stop, and step argument that generate trial output candidates that are checked against S_in and S_ex to see if they are allowed/filtered. An item is eligible for the final output if its value is present in at least one of our permitted baseline ranges S_in, unless that value happens to be intercepted by one of our forbidden exclusion ranges S_ex. On top of that structure, the outer sparse_range wrapper behaves like a native Python range—it accepts its own outer start, stop, and step constraints, matching the target window requested by the user.
To make the evaluation stateless and fast(?), the engine must evaluate values on the fly. However, native Python range objects are immutable and opaque; they don't natively maintain operational iteration states, nor do they know how to coordinate with one another.
To bridge this gap, the engine internally upgrades every raw Python range passed to collections S_in and S_ex into custom RangePointers.
Why We Need RangePointer and Direction Alignment
When the outer loop steps through values, the internal component ranges must follow along. If the outer loop is counting upwards (step > 0), all internal evaluation cursors must track upwards. If the outer loop reverses direction and counts downwards (step < 0), every single internal range must generate the same values, but in the reverse order, and without using up extra memory for storing values.
If they don't match directions, the leapfrogging logic breaks. A forward-running pointer cannot efficiently tell a backward-running outer loop where the next valid alignment boundary is without wasting CPU cycles scanning dead space or whatever.
Therefore, when a sparse_range instance is invoked, the engine inspects the outer step direction and enforces a unified traversal direction across every single internal S_in and S_ex sequence. If an internal range's native direction opposes the outer loop, it must be completely mathematically inverted.
The Range Reversal Formula
Reversing an arithmetic progression isn't as simple as swapping the start and stop boundaries. Because Python ranges stop before reaching the termination boundary, reversing a range requires recalculating its new alignment based on its exact step constraints.
To reverse a range mathematically, the engine computes:
New Step: The step sign is flipped (
-step).New Start: The final valid item generated by the original range becomes the new starting point. This is computed using the remainder of the length of the sequence:
new_start = start + (length - 1) × stepNew Stop: The original
startboundary shifts out by one step inverted to act as the non-inclusive terminal wall:new_stop = start - step
By normalizing all RangePointer instances to track in the exact same direction as the outer loop, the engine can efficiently step through candidate numbers, skipping blocks of excluded data in O(1) or O(K) time (where K is the number of active ranges), keeping the memory footprint at absolute zero.
The code
sparse_range.py
A run of included tests
Running Factory Pattern Limits Verification Suite (-1000 to +1000)...
✅ PASS: Standard positive crawl -> args: (0, 200, 2)
✅ PASS: Negative direction crawl -> args: (400, -400, -5)
✅ PASS: Large step slice jump -> args: (-1000, 1000, 25)
✅ PASS: Completely out of bounds window -> args: (600, 900, 1)
✅ PASS: Empty S_in Edge Case -> args: (0, 50, 1)
Test Summary: 5/5 passed successfully.
The Ultimate Stress Test: A Declarative Sieve
It's easy to write a basic range filter that skips a static block of numbers. But to prove that this RangePointer inversion and alignment math is completely airtight across dozens of concurrent boundaries, I decided to implement a completely declarative Sieve of Eratosthenes.
Instead of allocating a large mutable array of booleans in memory and procedurally striking out composites, we can use pure sequence logic:
The Permitted Range S_in: Our baseline pool of candidate of ints starting at 2:
[range(2, limit + 1)].The Exclusion ranges S_ex: A list of independent arithmetic progression ranges tracking the composites for every factor m up to √(limit).
Each exclusion range/stream starts at m × m (instead of m × 2). Any composite smaller than m × m has a prime factor smaller than m, meaning it has already been intercepted by an earlier stream. For example by the time m=5 runs, numbers like 10, 15, and 20 are already masked out by the ranges/streams for 2 and 3. The first unseen composite is 5 × 5 = 25.
Putting it to the Test
Although the module sparse_range.py has its own tests, to further verify the integrity of the design I put together a test module called sparse_range_sieve_test.py. It packages the sieve creation into an isolated factory function and validates the output against a traditional, trusted array sieve across both a forward sweep and a reversed backward pass.
The Verdict
When running forward, the generator's internal pointers march sequentially alongside the main consumer loop.
The true architectural challenge happens during the backward traversal pass (step = -1). When executing prime_sieve_factory(limit, 1, -1), the engine must manage 13 independent composite exclusion ranges simultaneously (for m = 2, 3, ..., 14). Each sub-range pointer must instantly compute its upper boundary, snap precisely to its maximum valid multiple less than or equal to 200, and step backward in perfect synchronization without dropping or skipping values.
The execution checks out perfectly:
Beginning Sparse Range Sieve Evaluation Suite (N = 200)...
----------------------------------------------------------------
👉 Running Forward Traversal Validation...
✅ PASS: Forward matching. Found 46 primes.
👉 Running Backward Traversal Validation...
✅ PASS: Backward matching. Reversed sequences match perfectly.
If you are following the Python Discuss thread, this demonstrates that handling arbitrary sequence exclusions cleanly and statelessly via pure index-pointer stream math isn't just an elegant idea—it's highly reliable even under heavy algorithmic stress.
Disclosure
I used Gemini AI to help in this. I was able to state algorithms and get Gemini to fill in with implementations, state changes and get Gemini to implement those too. I am used to creating algorithms without AI, this time I was able to state what I wanted in some detail and have Gemini add yet more detail. I could show python, pseudo-code, r textual descriptions as needed and get Gemini to fill in. The tests went from my description to code by Gemini - the sieve example test idea and spec, and debug was by me with Gemini improving my limit on m from m*2 to m*m for example before implementing as told.
June 26, 2026
Talk Python to Me
#553: All of our tools
This episode is a fun crossover from our Python news and tips podcast, Python Bytes. We have had some big changes over there. Brian Okken has moved on and Calvin Hendryx-Parker has joined the show as the new co-host. To kick off this new era, we decided to do a longer and more personal episode called "All Our Tools". The idea is both of us talk about some of our most useful day-to-day developer and business owner tools that we think you all would find useful. It was so well received, that I'm bringing it to you all as a crossover episode. Enjoy and we hope you find something new and awesome to help you with your software and data science day to day.<br/> <br/> <strong>Episode sponsors</strong><br/> <br/> <a href='https://talkpython.fm/sentry'>Sentry Error Monitoring, Code talkpython26</a><br> <a href='https://talkpython.fm/devopsbook'>Python in Production</a><br> <a href='https://talkpython.fm/training'>Talk Python Courses</a><br/> <br/> <h2 class="links-heading mb-4">Links from the show</h2> <div><strong>@calvinhp@sixfeetup.social</strong>: <a href="https://sixfeetup.social/@calvin?featured_on=talkpython" target="_blank" >sixfeetup.social</a><br/> <strong>@calvinhp.com</strong>: <a href="https://bsky.app/profile/calvinhp.com?featured_on=talkpython" target="_blank" >bsky.app</a><br/> <strong>calvinhp.com</strong>: <a href="https://calvinhp.com?featured_on=talkpython" target="_blank" >calvinhp.com</a><br/> <br/> <strong>Original airing on Python Bytes</strong>: <a href="https://pythonbytes.fm/episodes/show/484/all-our-tools?featured_on=talkpython" target="_blank" >pythonbytes.fm</a><br/> <br/> <strong>pi</strong>: <a href="https://pi.dev/?featured_on=talkpython" target="_blank" >pi.dev</a><br/> <strong>superpowers</strong>: <a href="https://github.com/obra/superpowers/tree/main?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Warp.dev</strong>: <a href="http://Warp.dev?featured_on=talkpython" target="_blank" >Warp.dev</a><br/> <strong>OhMyZSH</strong>: <a href="https://ohmyz.sh/?featured_on=talkpython" target="_blank" >ohmyz.sh</a><br/> <strong>Commandbookapp.com</strong>: <a href="http://Commandbookapp.com?featured_on=talkpython" target="_blank" >Commandbookapp.com</a><br/> <strong>Blink</strong>: <a href="https://blink.sh/?featured_on=talkpython" target="_blank" >blink.sh</a><br/> <strong>kitty</strong>: <a href="https://sw.kovidgoyal.net/kitty/?featured_on=talkpython" target="_blank" >sw.kovidgoyal.net</a><br/> <strong>mosh</strong>: <a href="https://mosh.org/?featured_on=talkpython" target="_blank" >mosh.org</a><br/> <strong>tmux</strong>: <a href="https://github.com/tmux/tmux?featured_on=talkpython" target="_blank" >github.com</a><br/> <strong>Claude code</strong>: <a href="https://www.anthropic.com/product/claude-code?featured_on=talkpython" target="_blank" >www.anthropic.com</a><br/> <strong>Claude.md</strong>: <a href="http://Claude.md?featured_on=talkpython" target="_blank" >Claude.md</a><br/> <strong>MacWhisper</strong>: <a href="https://goodsnooze.gumroad.com/l/macwhisper?featured_on=talkpython" target="_blank" >goodsnooze.gumroad.com</a><br/> <strong>Handy</strong>: <a href="https://handy.computer?featured_on=talkpython" target="_blank" >handy.computer</a><br/> <strong>Tailscale</strong>: <a href="https://tailscale.com/?featured_on=talkpython" target="_blank" >tailscale.com</a><br/> <strong>Talk Python episode with Alex</strong>: <a href="https://talkpython.fm/episodes/show/546/self-hosting-apps-for-python-people" target="_blank" >talkpython.fm</a><br/> <strong>Telescopo</strong>: <a href="https://www.telescopo.app?featured_on=talkpython" target="_blank" >www.telescopo.app</a><br/> <strong>Typora markdown</strong>: <a href="https://typora.io/?featured_on=talkpython" target="_blank" >typora.io</a><br/> <strong>formal documentation for many of my open source packages</strong>: <a href="https://mkennedy.codes/docs/?featured_on=talkpython" target="_blank" >mkennedy.codes</a><br/> <strong>Great Docs</strong>: <a href="https://posit-dev.github.io/great-docs/?featured_on=talkpython" target="_blank" >posit-dev.github.io</a><br/> <strong>Statement on the US government directive to suspend access to Fable 5 and Mythos 5</strong>: <a href="https://www.anthropic.com/news/fable-mythos-access?featured_on=talkpython" target="_blank" >www.anthropic.com</a><br/> <strong>No second date</strong>: <a href="https://x.com/pr0grammerhum0r/status/2063078450311598430?s=12&featured_on=talkpython" target="_blank" >x.com</a><br/> <br/> <strong>Watch this episode on YouTube</strong>: <a href="https://www.youtube.com/watch?v=wgKF3yvpxPU" target="_blank" >youtube.com</a><br/> <strong>Episode #553 deep-dive</strong>: <a href="https://talkpython.fm/episodes/show/553/all-of-our-tools#takeaways-anchor" target="_blank" >talkpython.fm/553</a><br/> <strong>Episode transcripts</strong>: <a href="https://talkpython.fm/episodes/transcript/553/all-of-our-tools" target="_blank" >talkpython.fm</a><br/> <br/> <strong>Theme Song: Developer Rap</strong><br/> <strong>🥁 Served in a Flask 🎸</strong>: <a href="https://talkpython.fm/flasksong" target="_blank" >talkpython.fm/flasksong</a><br/> <br/> <strong>---== Don't be a stranger ==---</strong><br/> <strong>YouTube</strong>: <a href="https://talkpython.fm/youtube" target="_blank" ><i class="fa-brands fa-youtube"></i> youtube.com/@talkpython</a><br/> <br/> <strong>Bluesky</strong>: <a href="https://bsky.app/profile/talkpython.fm" target="_blank" >@talkpython.fm</a><br/> <strong>Mastodon</strong>: <a href="https://fosstodon.org/web/@talkpython" target="_blank" ><i class="fa-brands fa-mastodon"></i> @talkpython@fosstodon.org</a><br/> <strong>X.com</strong>: <a href="https://x.com/talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @talkpython</a><br/> <br/> <strong>Michael on Bluesky</strong>: <a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" >@mkennedy.codes</a><br/> <strong>Michael on Mastodon</strong>: <a href="https://fosstodon.org/web/@mkennedy" target="_blank" ><i class="fa-brands fa-mastodon"></i> @mkennedy@fosstodon.org</a><br/> <strong>Michael on X.com</strong>: <a href="https://x.com/mkennedy?featured_on=talkpython" target="_blank" ><i class="fa-brands fa-twitter"></i> @mkennedy</a><br/></div>
The No Title® Tech Blog
Just updated - Optimize Images v2.1.0
Optimize Images 2.1.0 brings native WebP support, a generalized format-conversion system, on-demand image inspection with EXIF reporting, and a new in-memory API for working with image bytes directly. It is a focused, fully backwards-compatible step forward for the command-line tool and its public API.
Bob Belderbos
There Is No Magic: An AI Agent in 60 Lines of Python
Everybody talks about agents, and a lot of people assume they're some new kind of model. They aren't. An agent is a small amount of plumbing around an LLM you already understand. Let's build one from scratch in Python and see exactly what that plumbing is.
The formula
An agent is: Model + Instructions + Memory + Tools + Execution Loop.
Five parts. None of them is magic. The model is a brain in a jar: useful, fast, but stateless. It generates text; the code around it decides what to do with that text. That second half is the entire job and it's code we can reason about.
I made the same argument about the control layer being the real product. Here it is as a program.
Start with the model. A real one calls an LLM API; we use a fake one that satisfies the same interface:
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class Say:
text: str
@dataclass(frozen=True)
class Call:
tool: str
arg: str
Reply = Say | Call
class Model(Protocol):
def respond(self, system: str, history: list[str]) -> Reply: ...
The Model protocol has a single method, respond, which takes the system prompt and the conversation history and returns a Reply. It's a Protocol, so any object with a matching respond method counts as a Model, no inheritance required.
For this minimal agent, the Reply type captures the two actions we support: say something to the user, or call a tool with an argument. The model is free to return either one, and the agent will execute it. (Real models can also emit plans, ask clarifying questions, or request several tool calls at once; we keep it to two to stay legible.)
The agent's entire decision space is those two variants. The match in the loop below reads as a clean two-way branch, one case per reply, instead of a tangle of flags.
from dataclasses import dataclass, field
from typing import Callable
Tool = Callable[[str], str]
@dataclass
class Agent:
model: Model # 1. Model
system: str # 2. Instructions
history: list[str] = field(default_factory=list) # 3. Memory
tools: dict[str, Tool] = field(default_factory=dict) # 4. Tools
In this example, a tool is a function taking a string and returning a string. The agent holds the other four parts as plain fields:
- The model is any object satisfying the
Modelprotocol: a fake model goes in for testing and a real one for production. - The system prompt is a string that tells the model what to do.
- The history is the agent's working memory: the conversation and tool outputs that get replayed back into the model. Real agents often add retrieval, summarization, or external state on top, because context windows are finite.
- The tools field is a mapping of tool names to functions that implement them.
The loop is the agent
The part that turns a well-instructed chatbot into something agent-like is the fifth piece: an execution loop that lets the model observe outcomes and decide what to do next. Observe, think, act, check, repeat. Greatly simplified, of course, but this is the piece that does the work.
Because the model is stateless, the agent must keep track of what happened and feed the history back into the model until the model decides the job is done.
def run(self, user_input: str) -> str:
self.history.append(f"user: {user_input}")
while True: # real agents cap the iterations; see termination guards below
match self.model.respond(self.system, self.history):
case Say(text):
self.history.append(f"agent: {text}")
return text
case Call(tool, arg):
fn = self.tools.get(tool)
result = fn(arg) if fn else f"no such tool: {tool}"
self.history.append(f"tool[{tool}]: {result}")
# loop again: the model sees the result and decides what's next
Read it as the cycle:
- Observe: append the input.
- Think: ask the model.
- Act: if it asked for a tool, run the tool.
- Check and repeat: feed the result back into the history and loop, so the model sees what happened and decides whether it needs another tool or can finally answer.
There is no separate "check" block in the code. The check happens implicitly when the loop restarts and calls respond again with the new history. That step is the one that matters, because a model has no native sense of when a job is finished, and nothing stops it from asking for one more tool forever. The loop keeps going until the model returns Say instead of Call.
To run the whole thing without an API key, swap in a fake model and a real tool:
from pathlib import Path
def read_file(path: str) -> str:
try:
return f"{len(Path(path).read_text())} bytes"
except OSError as e:
return f"error: {e}"
class FakeModel:
def respond(self, system: str, history: list[str]) -> Reply:
last = history[-1] if history else ""
if last.startswith("tool["):
return Say(f"Done: {last}")
if last.startswith("user: read "):
return Call("read_file", last.removeprefix("user: read ").strip())
return Say("I can read files. Try: read <path>")
Wire it into a small main that builds the agent, reads a line, calls agent.run, and prints the reply:
def main() -> None:
agent = Agent(
model=FakeModel(),
system="You can read files.",
tools={"read_file": read_file},
)
while True:
try:
line = input("> ")
except EOFError:
break
print(agent.run(line.strip()))
if __name__ == "__main__":
main()
Now you can talk to it with no API key. Run it with python agent.py and type at the prompt:
> read pyproject.toml
Done: tool[read_file]: 76 bytes
That one exchange is a complete agent loop: the model asked for a tool, the loop ran it, fed the byte count back, and the model wrapped up on the second pass. The main thing standing between it and a real one is replacing FakeModel.respond with an HTTP call that returns the same Reply.
The whole thing as one runnable file is here as a GitHub gist. Save it, run python agent.py, and type at the prompt.
What this earns you
Sure, this is a simplified example, and the hard parts are exactly what FakeModel stubs out: prompt design, retries, tool schemas, context compaction, error recovery, and termination guards that stop the loop when a model keeps hallucinating tools. But the core of an agent is 60 lines and easy to reason about. The engineering lives in the control layer around the model.
Build the loop by hand once and frameworks stop feeling magical. LangChain's agent executor, AutoGen's shared memory, a coding agent's plan mode are all variations on these same five parts: engineering tradeoffs, not magic.
Keep reading
- The control layer is the product
- How an AI expense agent is actually structured
- Building an AI Agent in 6 Weeks (and Finally Understanding How They Work)
June 25, 2026
Artem Golubin
Hexora v0.3: New features and improvements
Recently, I've improved my Python library, hexora. I wrote it to detect malicious Python code using static analysis.
In the new v.0.3.0 release, I've added new detections, and we now also use a simple machine learning model to analyze the whole file. The machine learning model uses code structure features, semantic features, and static code analysis to assess the entire Python file.
Although the model can detect malicious code without any detections coming from static analysis, its main use case is to filter false positives.
I've been testing it against newly published PyPI packages and it detects 2-10 new malicious packages each day.

Due to the number of published packages, before the machine learning model, I was getting around 5-10 false positives for 1[......]
Django Weblog
How the Django Software Foundation Became a CNA
Why the DSF pursued CNA status
Django has a long history of responsible security practices: a dedicated, private security mailing list, clear advisory policies, and predictable security releases. Even so, we relied on external organizations to assign CVE IDs (Common Vulnerabilities and Exposures). This sometimes introduced administrative delays and extra coordination overhead.
Becoming a CNA (CVE Numbering Authority) allows the DSF to:
- Assign CVEs ourselves for vulnerabilities in Django and selected community projects.
- Publish advisories more efficiently and in closer alignment with Django's established release workflow.
- Maintain strong independence in how Django handles security incidents.
The initial exploration
The process began with internal discussions within the DSF Board and Django Security Team. We evaluated:
- Whether our existing security process already met CNA expectations.
- Whether we had the organizational stability to take on long term responsibility for CVE assignment.
- The scope of projects we would cover.
- How to ensure we could meet the operational requirements without overloading volunteers and Django Fellows.
After confirming that our policies were mature and that the administrative workload would be manageable, we initiated the CNA application with MITRE.
Preparing the application
MITRE requires that new CNAs document their security processes and demonstrate that they can meet CNA obligations. Our preparation included:
-
Reviewing and updating the Django Security Policy.
-
Mapping our existing workflows to MITRE's CNA rules, including:
- How reports are received.
- How vulnerabilities are validated.
- How advisories are produced.
- How CVEs will be assigned and published.
-
Defining the scope of the CNA:
- Django itself as the core product.
- A small, clearly bounded set of related ecosystem projects.
-
Ensuring we had private communication channels and documented procedures for confidential handling.
-
Drafting the required procedural documentation for MITRE.
Most of the work here was not about creating new processes but about articulating long standing Django practices in the format MITRE expects.
Training and review
Once our initial documentation was accepted, MITRE scheduled us for CNA onboarding training. This covered:
- CNA rules and responsibilities.
- Required data fields for CVE Records.
- Expectations for coordination with reporters and downstream consumers.
- The publication workflow for CVE Records.
We also completed MITRE's required CNA onboarding exercises. As part of this process, we worked through sample security reports and demonstrated how we would determine CVE assignments, including cases where multiple CVEs may or may not be warranted for a single report.
Approval and onboarding
After MITRE approved our documentation, training, and exercise submissions, the DSF was formally granted CNA status. The announcements steps were:
- MITRE published our CNA scope on the official CNA directory.
- MITRE issued a press release.
- The DSF published a blog post announcing our new CNA status.
Lessons learned
A few procedural insights for other projects considering CNA status:
- If your project already has a mature and documented security process, becoming a CNA is mostly about formalizing what you already do.
- The documentation and validation steps take time. Most delays come from ensuring that all fields conform to the CVE schema. The whole process took about four months.
- The training is detailed and helpful. It clarifies exactly what CNAs are expected to produce and how CVE Records flow through the broader ecosystem.
- It is worth being explicit about scope early. Defining the boundary of responsibility reduces ambiguity later.
What changes for Django users
For most contributors and users, nothing changes. Django will continue to follow its established process for receiving reports, coordinating fixes, and publishing security releases.
The difference is that the DSF can now assign CVE IDs directly, which simplifies coordination and allows us to publish advisories with fewer external dependencies.
Acknowledgments
This work was led by Django Fellows Natalia Bidart and Jacob Walls, with support from the Django Security Team and the DSF Board. We are grateful to MITRE for their guidance during the onboarding process.
If you have questions about Django's CNA scope or security process, contact the Django Security Team.
PyCharm
Explicit Lazy Imports Are Coming to Python 3.15
A while ago at PyCon US 2026, I had the pleasure of listening to the Python Steering Council give updates about new features that are being added in Python 3.15. One that stood out was explicit lazy imports (via PEP 810), which defer module loading until first use. I am curious to see how this new feature works, and I want to benchmark its performance with PyCharm. Let’s take a look together.
Overview of explicit lazy imports
PEP 810 introduces an explicit syntax for lazy imports, allowing you to defer the loading and execution of modules until their attributes are actually accessed, unlike standard eager imports that execute immediately. This feature aims to significantly reduce startup latency and memory consumption. Explicitly marking modules as `lazy` can deliver substantial improvements in initial responsiveness and baseline resource usage in large-scale applications and command-line tools.
Because the implementation approach uses proxy objects within the module’s namespace instead of modifying Python’s fundamental dictionary structures, it preserves critical interpreter optimizations.
This mechanism defers both the finding and the loading of the module to maximize efficiency, especially in environments with high-latency filesystems. To manage potential side effects and ensure backward compatibility, the proposal includes global control flags and a transitional variable for progressive adoption across different Python versions.
In short, Python 3.15 will let you optimize application performance by significantly reducing startup latency and memory consumption, as the loading and execution of modules are deferred until their attributes are actually accessed.
Trying them out in Python 3.15.0b1
At the time this is being written, Python 3.15.0b1 is already out, so we can give this new feature a try. You can build it from source at the CPython GitHub repo, but since getting Python 3.15.0b1 is easy when using `uv` or `pyenv`, we will do that instead.
Make sure you have the latest version of `uv` or `pyenv`, and then download Python 3.15.0b1 via either of the following commands:
- `uv python install 3.15.0b1`
- `pyenv install 3.15.0b1`
After that, select the new interpreter in your project in PyCharm.
Now you will need to reinstall the dependencies for your project. You may have to build some of the libraries from source, as most of the libraries will not have a Python 3.15 wheel for download.
Profiling against normal imports
It is a common joke that the first thing data scientists will do is type `import pandas as pd` and `import numpy as np`, even if they are not actually going to use them. Let’s assume this is the case, and you received a script like this from your colleague:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
def main():
print("Initializing example data science project...")
# Generate some dummy data
data = {
'x': np.linspace(0, 10, 100),
'y': np.sin(np.linspace(0, 10, 100)) + np.random.normal(0, 0.1, 100)
}
# Plotting
plt.figure(figsize=(10, 6))
plt.plot(data['x'], data['y'], label='Sine Wave with Noise')
plt.title('Sample Visualization')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.legend()
# Save the plot instead of showing it (since this is non-interactive)
plt.savefig('sine_wave.png')
print("Project executed successfully. Plot saved as sine_wave.png.")
if __name__ == "__main__":
main()
As you see, PyCharm highlights the unused pandas import for you, so removing it would be straightforward. However, for our experiment here, we’ll keep it.
To get a better visualization of import profiles, install a tool from PyPI called tuna.
You can profile your script by setting a custom run script with this script text:
python -X importtime main.py 2> import_log.txt; tuna import_log.txt
When you use it, a new browser window will pop up with the import graph.
As you see, importing pandas accounts for half of the time it takes to load all the modules, and we never use it!
Now let’s add `lazy` to all the imports.
Don’t worry about the syntax highlighting. PyCharm just doesn’t recognize it yet since `lazy` is a new keyword that has not been officially released.
Let’s profile the script again.
Now we see the pandas import is gone, and loading everything takes way less time.
So, if you have a script that imports a lot of large libraries, and some of them are only used in certain conditions (e.g. in if-else clauses), lazy import can save time by loading modules only when they are first used.
Checking the inner workings with lazy imports
Let’s see how lazy imports are handled internally.
When a module is imported “lazily”, meaning `__lazy_import__` is called instead of `__import__`, a `types.LazyImportType` proxy object will be created. The module name will then be listed in `sys.lazy_modules` instead of `sys.modules`. (See the Lazy import mechanism section in PEP 810.)
When a lazy object is used, it needs to be reified. CPython will try resolving the import at that point and replacing the proxy object with the actual module itself. In this process, `__import__` is called to resolve the import. At the same time, the module is removed from `sys.lazy_modules`.
If there’s an error during reification, AKA importing the module, the lazy object is not reified or replaced. The next time the lazy module is used, the import will try again. The exception raised during reification will also show both where the lazy import was defined and where it was accessed. (See the Reification section in PEP 810.)
To experiment with it ourselves, let’s add some breakpoints with `pdb` and check what’s happening in the code:
import pdb pdb.set_trace() lazy import pandas as pd lazy import matplotlib.pyplot as plt lazy import numpy as np pdb.set_trace() …
And
# Generate some dummy data
data = {
'x': np.linspace(0, 10, 100),
'y': np.sin(np.linspace(0, 10, 100)) + np.random.normal(0, 0.1, 100)
}
pdb.set_trace()
# Plotting
plt.figure(figsize=(10, 6))
pdb.set_trace()
…
Now run the script in the console:
python main.py
Note that PyCharm 2026.1 does not yet support Python 3.15, so using the Run or Debug button to run a script using lazy import may result in unexpected behavior.
When it hits the first line of ` pdb.set_trace()` at the top, there should not be any module loaded in. Let’s check:
(Pdb) import sys (Pdb) sys.lazy_modules
As expected, none of our libraries – pandas, numpy, and matplotlib – are listed.
Now, let’s continue running the program and let it stop at the next breakpoint. In the console, type `continue` and once it stops, we can check by typing `sys.lazy_modules` again:
Here, we see that all of our modules are in `lazy_modules`. Let’s check whether pandas is in `sys.modules`:
(Pdb) 'pandas' in sys.modules
Nope, it’s not. You can try with numpy and matplotlib, and you will see that neither of those is in `sys.module`.
Now let’s type `continue` again and reach the next breakpoint, which occurs after numpy is used. Check `sys.lazy_module` again, and you’ll see that numpy is no longer on the list. When we check whether it is in `sys.module`, we get `True` this time.
However, pandas and matplotlib are still not in `sys.modules`.
When you check the next breakpoint, you’ll see that matplotlib is similarly removed from `sys.lazy_modules` and added to `sys.modules` after it is used.
Trying it yourself with PyCharm
Download the latest version of PyCharm to experiment with Python 3.15.0b1 and experience firsthand how explicit lazy imports can optimize your application’s performance by significantly reducing startup latency and memory consumption.

