Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
153046 stories
·
33 followers

AWS Weekly Roundup: Kiro CLI latest features, AWS European Sovereign Cloud, EC2 X8i instances, and more (January 19, 2026)

1 Share

At the end of 2025 I was happy to take a long break to enjoy the incredible summers that the southern hemisphere provides. I’m back and writing my first post in 2026 which also happens to be my last post for the AWS News Blog (more on this later).

The AWS community is starting the year strong with various AWS re:invent re:Caps being hosted around the globe, with some communities already hosting their AWS Community Day events, the AWS Community Day Tel Aviv 2026 was hosted last week.

Last week’s launches
Here are last week’s launches that caught my attention:

  • Kiro CLI latest features – Kiro CLI now has granular controls for web fetch URLs, keyboard shortcuts for your custom agents, enhanced diff views, and much more. With these enhancements, you can now use allowlists or blocklists to restrict which URLs the agent can access, ensure a frictionless experience when working with multiple specialized agents in a single session, to name a few.
  • AWS European Sovereign Cloud – Following an announcement in 2023 of plans to build a new, independent cloud infrastructure, last week we announced the general availability of the AWS European Sovereign Cloud to all customers. The cloud is ready to meet the most stringent sovereignty requirements of European customers with a comprehensive set of AWS services.
  • Amazon EC2 X8i instancesPreviously launched in preview at AWS re:Invent 2025, last week we announced the general availability of new memory-optimized Amazon Elastic Compute Cloud (Amazon EC2) X8i instances. These instances are powered by custom Intel Xeon 6 processors with a sustained all-core turbo frequency of 3.9 GHz, available only on AWS. These SAP certified instances deliver the highest performance and fastest memory bandwidth among comparable Intel processors in the cloud.

Additional updates
These projects, blog posts, and news articles also caught my attention:

  • 5 core features in Amazon Quick Suite – AWS VP Agentic AI Swami Sivasubramanian talks about how he uses Amazon Quick Suite for just about everything. In October 2025 we announced Amazon Quick Suite, a new agentic teammate that quickly answers your questions at work and turns insights into actions for you. Amazon Quick Suite has become one of my favorite productivity tools, helping me with my research on various topics in addition to providing me with multiple perspectives on a topic.
  • Deploy AI agents on Amazon Bedrock AgentCore using GitHub Actions – Last year we announced Amazon Bedrock AgentCore, a flexible service that helps you seamlessly create and manage AI agents across different frameworks and models, whether hosted on Amazon Bedrock or other environments. Learn how to use a GitHub Actions workflow to automate the deployment of AI agents on AgentCore Runtime. This approach delivers a scalable solution with enterprise-level security controls, providing complete continuous integration and delivery (CI/CD) automation.

Upcoming AWS events
Join us January 28 or 29 (depending on your time zone) for Best of AWS re:Invent, a free virtual event where we bring you the most impactful announcements and top sessions from AWS re:Invent. Jeff Barr, AWS VP and Chief Evangelist, will share his highlights during the opening session.

There is still time until January 21 to compete for $250,000 in prizes and AWS credits in the Global 10,000 AIdeas Competition (yes, the second letter is an I as in Idea, not an L as in like). No code required yet: simply submit your idea, and if you’re selected as a semifinalist, you’ll build your app using Kiro within AWS Free Tier limits. Beyond the cash prizes and potential featured placement at AWS re:Invent 2026, you’ll gain hands-on experience with next-generation AI tools and connect with innovators globally.

Earlier this month, the 2026 application for the Community Builders program launched. The application is open until January 21st, midnight PST so here’s your last chance to ensure that you don’t miss out.

If you’re interested in these opportunities, join the AWS Builder Center to learn with builders in the AWS community.

With that, I close one of my most meaningful chapters here at AWS. It’s been an absolute pleasure to write for you and I thank you for taking the time to read the work that my team and I pour our absolute hearts into. I’ve grown from the close collaborations with the launch teams and the feedback from all of you. The Sub-Sahara Africa (SSA) community has grown significantly, and I want to dedicate more time focused on this community, I’m still at AWS and I look forward to meeting at an event near you!

Check back next Monday for another Weekly Roundup!

Veliswa Boya

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

#466 PSF Lands $1.5 million

1 Share
Topics covered in this episode:
Watch on YouTube

About the show

Sponsored by us! Support our work through:

Connect with the hosts

Join us on YouTube at pythonbytes.fm/live to be part of the audience. Usually Monday at 11am PT. 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 our friends of the show list, we'll never share it.

Brian #1: Better Django management commands with django-click and django-typer

Michael #2: PSF Lands a $1.5 million sponsorship from Anthropic

  • Anthropic is partnering with the Python Software Foundation in a landmark funding commitment to support both security initiatives and the PSF's core work.
  • The funds will enable new automated tools for proactively reviewing all packages uploaded to PyPI, moving beyond the current reactive-only review process.
  • The PSF plans to build a new dataset of known malware for capability analysis
  • The investment will sustain programs like the Developer in Residence initiative, community grants, and infrastructure like PyPI.

Brian #3: How uv got so fast

  • Andrew Nesbitt
  • It’s not just be cause “it’s written in Rust”.
  • Recent-ish standards, PEPs 518 (2016), 517 (2017), 621 (2020), and 658 (2022) made many uv design decisions possible
  • And uv drops many backwards compatible decisions kept by pip.
  • Dropping functionality speeds things up.
    • “Speed comes from elimination. Every code path you don’t have is a code path you don’t wait for.”
  • Some of what uv does could be implemented in pip. Some cannot.
  • Andrew discusses different speedups, why they could be done in Python also, or why they cannot.
  • I read this article out of interest. But it gives me lots of ideas for tools that could be written faster just with Python by making design and support decisions that eliminate whole workflows.

Michael #4: PyView Web Framework

Extras

Brian:

  • Upgrade Django, has a great discussion of how to upgrade version by version and why you might want to do that instead of just jumping ahead to the latest version. And also who might want to save time by leapfrogging
    • Also has all the versions and dates of release and end of support.
  • The Lean TDD book 1st draft is done.
    • Now available through both pythontest and LeanPub
      • I set it as 80% done because of future drafts planned.
    • I’m working through a few submitted suggestions. Not much feedback, so the 2nd pass might be fast and mostly my own modifications. It’s possible.
    • I’m re-reading it myself and already am disappointed with page 1 of the introduction. I gotta make it pop more. I’ll work on that.
    • Trying to decide how many suggestions around using AI I should include.
      • It’s not mentioned in the book yet, but I think I need to incorporate some discussion around it.

Michael:

Joke: Reverse Superman





Download audio: https://pythonbytes.fm/episodes/download/466/psf-lands-1.5-million.mp3
Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Convert CSV to PDF in .NET C#

1 Share
Learn how to convert CSV data to a table in C# using the ServerTextControl library with this step-by-step tutorial. Easily generate PDF documents from CSV files in your .NET applications.

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Step-by-Step Guide: Build a CRUD Blazor App with Entity Framework and PostgreSQL

1 Share

Introduction

Happy New Year 2026 to all! This year, I want to share more basic must-learn knowledge on .NET. This tutorial is my first sharing on dev.to this year. Happy to see you all again.

Entity Framework (EF) is Microsoft's object-relational mapper (ORM) for .NET. It simplifies data access by allowing you to work with databases using .NET objects, eliminating the need for most data-access code. In this tutorial, we'll build a simple CRUD (Create, Read, Update, Delete) Blazor Server app using EF Core with PostgreSQL. We'll cover setting up the project, defining models, seeding data, and creating a basic UI for managing customers.

By the end, you'll have a functional app demonstrating EF's power for database operations.

Prerequisites

Before we begin, ensure you have the following:

  • Visual Studio or any C# IDE (e.g., VS Code).
  • .NET 10 SDK installed (download from Microsoft's site).
  • PostgreSQL installed and running (download from EnterpriseDB). Set up a database (e.g., "BlazorDemo") and note the connection details.

Step 1: Setting Up the Blazor Project

Create a new Blazor Server app:

dotnet new blazorserver --force

This scaffolds a basic Blazor Server project.

Step 2: Adding Entity Framework Packages

Add the necessary NuGet packages for EF Core and PostgreSQL:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
  • Microsoft.EntityFrameworkCore: Core EF functionality.
  • Microsoft.EntityFrameworkCore.Tools: For migrations and database commands.
  • Npgsql.EntityFrameworkCore.PostgreSQL: PostgreSQL provider.

Step 3: Creating Models

Define your data models. Create a Models folder and add classes like Customer.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorWithEntityFramework.Models
{
    public class Customer
    {
        [Key]
        public long CustomerId { get; set; }

        [Required]
        [StringLength(1000)]
        public string CustomerNumber { get; set; } = string.Empty;

        [Required]
        [StringLength(100)]
        public string LastName { get; set; } = string.Empty;

        [Required]
        [StringLength(100)]
        public string FirstName { get; set; } = string.Empty;

        [Required]
        public DateTime Dob { get; set; }

        public bool IsDeleted { get; set; } = false;

        [StringLength(100)]
        public string CreateBy { get; set; } = "SYSTEM";

        public DateTime CreateDate { get; set; } = DateTime.UtcNow;

        [StringLength(100)]
        public string ModifyBy { get; set; } = "SYSTEM";

        public DateTime ModifyDate { get; set; } = DateTime.UtcNow;
    }
}

Product.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorWithEntityFramework.Models
{
    public class Product
    {
        [Key]
        public long ProductId { get; set; }

        [Required]
        [StringLength(1000)]
        public string ProductName { get; set; } = string.Empty;

        [Required]
        [StringLength(1000)]
        public string ProductCode { get; set; } = string.Empty;

        [Required]
        [Range(0, int.MaxValue)]
        public int AvailableQuantity { get; set; }

        public bool IsDeleted { get; set; } = false;

        [StringLength(100)]
        public string CreateBy { get; set; } = "SYSTEM";

        public DateTime CreateDate { get; set; } = DateTime.UtcNow;

        [StringLength(100)]
        public string ModifyBy { get; set; } = "SYSTEM";

        public DateTime ModifyDate { get; set; } = DateTime.UtcNow;

        public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
    }
}

Order.cs:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlazorWithEntityFramework.Models
{
    public class Order
    {
        [Key]
        public long OrderId { get; set; }

        public long? CustomerId { get; set; }

        [Required]
        [StringLength(1000)]
        public string OrderNumber { get; set; } = string.Empty;

        [Required]
        public DateTime OrderDate { get; set; }

        public bool IsDeleted { get; set; } = false;

        [StringLength(100)]
        public string CreateBy { get; set; } = "SYSTEM";

        public DateTime CreateDate { get; set; } = DateTime.UtcNow;

        [StringLength(100)]
        public string ModifyBy { get; set; } = "SYSTEM";

        public DateTime ModifyDate { get; set; } = DateTime.UtcNow;

        [ForeignKey("CustomerId")]
        public Customer? Customer { get; set; }

        public ICollection<OrderItem> OrderItems { get; set; } = new List<OrderItem>();
    }
}

OrderItem.cs:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlazorWithEntityFramework.Models
{
    public class OrderItem
    {
        [Key]
        public long OrderItemId { get; set; }

        public long? OrderId { get; set; }

        public long? ProductId { get; set; }

        [Required]
        [Range(1, int.MaxValue)]
        public int Quantity { get; set; }

        public bool IsDeleted { get; set; } = false;

        [StringLength(100)]
        public string CreateBy { get; set; } = "SYSTEM";

        public DateTime CreateDate { get; set; } = DateTime.UtcNow;

        [StringLength(100)]
        public string ModifyBy { get; set; } = "SYSTEM";

        public DateTime ModifyDate { get; set; } = DateTime.UtcNow;

        [ForeignKey("OrderId")]
        public Order? Order { get; set; }

        [ForeignKey("ProductId")]
        public Product? Product { get; set; }
    }
}

Step 4: Setting Up the DbContext

Create a Data folder and add AppDbContext.cs:

using Microsoft.EntityFrameworkCore;
using BlazorWithEntityFramework.Models;

namespace BlazorWithEntityFramework.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

        public DbSet<Customer> Customers { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Seed data
            modelBuilder.Entity<Customer>().HasData(
                new Customer
                {
                    CustomerId = 1,
                    CustomerNumber = "CUST0001",
                    LastName = "Au Yeung",
                    FirstName = "David",
                    Dob = new DateTime(1980, 12, 31, 0, 0, 0, DateTimeKind.Utc),
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                },
                new Customer
                {
                    CustomerId = 2,
                    CustomerNumber = "CUST0002",
                    LastName = "Chan",
                    FirstName = "Peter",
                    Dob = new DateTime(1982, 1, 15, 0, 0, 0, DateTimeKind.Utc),
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                }
            );

            modelBuilder.Entity<Product>().HasData(
                new Product
                {
                    ProductId = 1,
                    ProductName = "Android Phone",
                    ProductCode = "A0001",
                    AvailableQuantity = 100,
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                },
                new Product
                {
                    ProductId = 2,
                    ProductName = "iPhone",
                    ProductCode = "I0001",
                    AvailableQuantity = 100,
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                }
            );

            modelBuilder.Entity<Order>().HasData(
                new Order
                {
                    OrderId = 1,
                    CustomerId = 1,
                    OrderNumber = "ORD0001",
                    OrderDate = DateTime.UtcNow,
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                }
            );

            modelBuilder.Entity<OrderItem>().HasData(
                new OrderItem
                {
                    OrderItemId = 1,
                    OrderId = 1,
                    ProductId = 2,
                    Quantity = 10,
                    IsDeleted = false,
                    CreateBy = "SYSTEM",
                    CreateDate = DateTime.UtcNow,
                    ModifyBy = "SYSTEM",
                    ModifyDate = DateTime.UtcNow
                }
            );
        }
    }
}

Step 5: Configuring the Connection String

In appsettings.json, add:

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=BlazorDemo;Username=yourusername;Password=yourpassword"
  }
}

Update Program.cs to register the DbContext:

builder.Services.AddDbContext<AppDbContext>(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

And ensure DB creation:

using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.EnsureCreated();
}

Step 6: Creating Migrations and Updating the Database

Generate and apply migrations:

dotnet ef migrations add InitialCreate
dotnet ef database update

EnsureCreated() in Program.cs ensures the database is created and seeded on startup. This works alongside migrations for a demo. Note that in production, you might prefer migrations exclusively for better control.

Step 7: Building the CRUD UI

Create a page like Pages/Customers.razor for managing customers:

@page "/customers"
@inject AppDbContext DbContext

<h3>Customers</h3>

@if (customers == null)
{
    <p>Loading...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Customer Number</th>
                <th>Last Name</th>
                <th>First Name</th>
                <th>DOB</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var customer in customers.Where(c => !c.IsDeleted))
            {
                <tr>
                    <td>@customer.CustomerId</td>
                    <td>@customer.CustomerNumber</td>
                    <td>@customer.LastName</td>
                    <td>@customer.FirstName</td>
                    <td>@customer.Dob.ToShortDateString()</td>
                    <td>
                        <button class="btn btn-primary" @onclick="() => EditCustomer(customer)">Edit</button>
                        <button class="btn btn-danger" @onclick="() => DeleteCustomer(customer)">Delete</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <button class="btn btn-success" @onclick="AddCustomer">Add New Customer</button>

    @if (isEditing)
    {
        <div class="modal" style="display:block;">
            <div class="modal-content">
                <h4>@(editingCustomer.CustomerId == 0 ? "Add" : "Edit") Customer</h4>
                <form>
                    <div class="form-group">
                        <label>Customer Number</label>
                        <input type="text" class="form-control" @bind="editingCustomer.CustomerNumber" />
                    </div>
                    <div class="form-group">
                        <label>Last Name</label>
                        <input type="text" class="form-control" @bind="editingCustomer.LastName" />
                    </div>
                    <div class="form-group">
                        <label>First Name</label>
                        <input type="text" class="form-control" @bind="editingCustomer.FirstName" />
                    </div>
                    <div class="form-group">
                        <label>DOB</label>
                        <input type="date" class="form-control" @bind="editingCustomer.Dob" />
                    </div>
                    <button type="button" class="btn btn-primary" @onclick="SaveCustomer">Save</button>
                    <button type="button" class="btn btn-secondary" @onclick="CancelEdit">Cancel</button>
                </form>
            </div>
        </div>
    }
}

@code {
    private List<Customer> customers = new();
    private Customer editingCustomer = new();
    private bool isEditing = false;

    protected override async Task OnInitializedAsync()
    {
        await LoadCustomers();
    }

    private async Task LoadCustomers()
    {
        customers = await DbContext.Customers.AsNoTracking().ToListAsync();
    }

    private void AddCustomer()
    {
        editingCustomer = new Customer { Dob = DateTime.UtcNow };
        isEditing = true;
    }

    private void EditCustomer(Customer customer)
    {
        editingCustomer = new Customer
        {
            CustomerId = customer.CustomerId,
            CustomerNumber = customer.CustomerNumber,
            LastName = customer.LastName,
            FirstName = customer.FirstName,
            Dob = customer.Dob,
            IsDeleted = customer.IsDeleted,
            CreateBy = customer.CreateBy,
            CreateDate = customer.CreateDate,
            ModifyBy = "USER",
            ModifyDate = DateTime.UtcNow
        };
        isEditing = true;
    }

    private async Task SaveCustomer()
    {
        if (editingCustomer.CustomerId == 0)
        {
            DbContext.Customers.Add(editingCustomer);
        }
        else
        {
            var existing = await DbContext.Customers.FindAsync(editingCustomer.CustomerId);
            if (existing != null)
            {
                DbContext.Entry(existing).CurrentValues.SetValues(editingCustomer);
            }
        }
        await DbContext.SaveChangesAsync();
        isEditing = false;
        await LoadCustomers();
    }

    private void CancelEdit()
    {
        isEditing = false;
    }

    private async Task DeleteCustomer(Customer customer)
    {
        var existing = await DbContext.Customers.FindAsync(customer.CustomerId);
        if (existing != null)
        {
            existing.IsDeleted = true;
            existing.ModifyBy = "USER";
            existing.ModifyDate = DateTime.UtcNow;
            DbContext.Customers.Update(existing);
        }
        await DbContext.SaveChangesAsync();
        await LoadCustomers();
    }
}

Add navigation in NavMenu.razor.

Step 8: Running the App

Run the app:

dotnet run

Navigate to /customers to test CRUD operations.

Conclusion

You've built a CRUD Blazor app with EF and PostgreSQL! EF simplifies database interactions, and Blazor provides a seamless UI. Experiment with more entities or features like validation.

Feel free to ask questions or expand on this. Happy coding!

Love C#!

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

The Introvert’s Guide to Networking: How I Stopped Faking It and Started Growing

1 Share

The "Alien" Phase

For a long time, I felt like an alien.

I’m naturally reserved. I’m not the loudest person in the room, and for years, I thought that was a defect. I grew up surrounded by people who didn't really get me. I found myself forcing an interest in things the "masses" liked, such as trends, generic hobbies, and surface-level conversations, just to survive social situations.

I was masking my true self to fit in, and it was exhausting. I thought, “Maybe I’m just not built for connecting with people.”

I wasn't anti-social, I just didn't have a social circle that spoke my language. I was starving for intellectual connection but looking for it in all the wrong places.

Finding My Voice on Dev.to

Then, I started writing here.

Initially, I thought Dev.to was just a place to dump code snippets. But as I began sharing my personal experiences, my learning journey, and my struggles, something shifted.

I realized I wasn't shouting into the void. I was speaking into a room full of people just like me.

Writing allowed me to express myself in a way speaking never did. It gave me the space to articulate my thoughts without being interrupted or feeling the pressure to be "cool." Suddenly, my obsession with tech, my curiosity about how things work, and my specific interests weren't "weird." They were the currency of this community.

The "ROI" of Putting Yourself Out There

Here is the crazy part: Networking doesn't always happen in a suit at a conference center. Sometimes it happens in the comment section.

Since I started consistently sharing my journey and being vulnerable about what I’m learning:

I’ve connected with amazing developers who share my niche interests.

I’ve had recruiters slide into my DMs because they liked how I explained a concept.

I’ve even landed paid freelance gigs purely because someone saw a post of mine and thought, "This person knows their stuff, and I like their vibe."

I didn't have to change who I was. I just had to change where I was speaking.

The Next Step: Stepping Into the Real World

Because this platform gave me the confidence to believe that my voice matters, I’m challenging myself to take the next step.

I am going to start attending in-person tech events.

Yes, as an introvert, the idea is terrifying. But now I know that when I walk into those rooms, I’m not walking in as an alien. I’m walking into a room full of potential readers, friends, and collaborators.

What’s Coming Next?

I want to take you along for this ride.

The DevOps Journey Series: I will continue writing my technical series on Linux and my journey to devOps (which I love writing), so stay tuned for those deep dives.

The Event Series: I will be starting a new series documenting my experiences at these in-person tech events. I’ll share what it’s actually like, how I handle the nerves, and who I meet.

You Are Not Alone!

If you are reading this and you feel like the "alien" in your circle, please know you aren't alone.

There is a massive community of people here who are interested in the exact same things you are. You don't need to force yourself to like what the masses like. You just need to find your tribe.

Start writing. Start sharing. You’ll be surprised who writes back :)

Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Make a CLI command from your Python package (quick guide)

1 Share

In short: declare a console_scripts entry point in your package metadata, activate the target virtual environment, then run pip install (during development use pip install -e .). That will place an executable wrapper in ./venv/bin (Unix/macOS) or venv\Scripts (Windows). Below are concrete examples and step-by-step instructions.

1) Example project layout

myproject/
├─ pyproject.toml   ← (or setup.cfg / setup.py)
└─ src/
   └─ cli.py

src/cli.py (example):

def main():
    print("Hello from mycmd!")

if __name__ == "__main__":
    main()

The function referenced by the entry point (here mypackage.cli:main) will be called when the installed command runs.

2) Recommended: pyproject.toml + setuptools

When using setuptools as the build backend, add a pyproject.toml like:

[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "0.0.1"
description = "example package"

[project.scripts]
mycmd = "cli:main"

This makes mycmd an installed command that calls cli:main.

3) Another common way: setup.cfg

If your project uses setup.cfg:

[metadata]
name = mypackage
version = 0.0.1

[options]
packages = find:

[options.entry_points]
console_scripts =
    mycmd = cli:main

(Older projects may still use setup.py with entry_points={...}.)

4) Install into a virtual environment (activate first!)

Always create and activate a venv before installing, so the wrapper lands in the venv and not in system Python.

Unix / macOS:

python -m venv venv
source venv/bin/activate
pip install -e .

Windows (PowerShell):

python -m venv venv
venv\Scripts\Activate.ps1
pip install -e .

-e (editable) is convenient during development because code edits are immediately reflected when you run the command. Using pip install . (non-editable) also creates the script/binary but installs a fixed build.

5) Verify the installed command

Unix/macOS:

which mycmd         # shows venv/bin/mycmd if venv is active
mycmd               # run it and check output

Expected locations:

  • Unix/macOS: ./venv/bin/mycmd (executable wrapper)
  • Windows: venv\Scripts\mycmd.exe (or mycmd-script.py)

Run mycmd and you should see Hello from mycmd! (or whatever your main() prints).

6) Notes & best practices

  • Always activate the virtual environment before pip install unless you explicitly want a system/global install.
  • console_scripts (entry points) are the recommended approach — they produce OS-appropriate wrapper scripts and integrate with package metadata.
  • scripts= (legacy setup.py option) copies scripts directly; entry points are generally better for dependency resolution and cross-platform behavior.
  • Libraries like Click or Typer make building richer CLI apps simple; the entry point syntax remains the same (point to the function that invokes the CLI).
Read the whole story
alvinashcraft
3 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories