Changelog
Each release, newest first: what changed, then under Upgrading what to change in code, commands or installs that use the previous release.
Version 0.28.0
SimulationRegistry.changesets a value and returns aParamChangethat says what was stored, or raisesParamRejectedwhen the registry refuses it. See Know Whether a Change Was Refused.on_change, on the registry and onSimulationContext, is a list of callbacks that receive aParamChangefor every accepted change, with its source: an ingress connector and its class, a scenario step, or the overrides ofstart(). See Follow Every Change.update,set_exactlyandstart()takesource,actorandreason, which eachParamChangecarries.SimulationContext.registry_paths()returns the paths and starting values of a run without running it. See Find the Path.FileIngress,type: Filein YAML, changes parameters during a run when you edit a local YAML file, with no service. See File and the File Steering example.- The AI skill and Use an AI Assistant teach how to draft a model from a description of a process.
Upgrading
- A scenario step whose value the registry refuses, such as an exponential rate of 0, now stops the load of a blueprint with its line, and
start()of a builder model.start()checks the scenario with its overrides, so an override or an experiment design that makes a later step's value invalid also stops the start. A run stops withParamRejectedat a step's time when an ingress message, anon:action or a process has made the step's value invalid, and a low-levelrun_scenarioraises it for any refused value. Before 0.28.0 the value was logged and ignored, and the run went on. Values sent by ingress connectors andon:actions are still logged and ignored. See Stability and Limits.
Version 0.27.0
GlueAvroSerializerandGlueAvroDeserializertalk to AWS Glue with desflow's own client and wire format, so theglueextra no longer depends on aws-glue-schema-registry. The arguments and the bytes on the topic are the same.- The
kafkaextra no longer installs kafka-python.KafkaAdminConnector.create_topicsand the topic creation ofKafkaEgressuse aiokafka. - JSON Schema and Protobuf are read and written through both schema registries, next to Avro, by
ConfluentJsonSchemaSerializer,ConfluentJsonSchemaDeserializer,ConfluentProtobufSerializer,ConfluentProtobufDeserializer,GlueJsonSchemaSerializer,GlueJsonSchemaDeserializer,GlueProtobufSerializerandGlueProtobufDeserializer. See JSON Schema and Protobuf. - The builder and low-level scripts of the Kafka, PostgreSQL, Redis and Related Records examples stop at
EXAMPLE_UNTILsimulation seconds when that variable is set, and run until Ctrl + C when it is not. - A new How-To group, Storage and Tables, has two pages: Write to Cloud Storage writes Parquet and JSON Lines files to S3, Google Cloud Storage or Azure Blob Storage, and Write to Iceberg Tables appends to a table through a SQLite, REST, PostgreSQL or Glue catalog. Each has YAML, Builder and Low-level forms.
Upgrading
- The
glueextra no longer installsaws-glue-schema-registry. Code that importsaws_schema_registryitself must add it to its own dependencies. - Glue schema registration waits up to 30 seconds (10 checks, 3 seconds apart) for a new schema version to become available. aws-glue-schema-registry 1.1.3 waited 3 seconds once and then checked 10 times with no wait between checks.
- Glue registry and framing failures raise desflow's
SchemaRegistryErrorandSerializationError, importable fromdesflow.connectors.egress.kafkaanddesflow.connectors.ingress.kafka, notaws_schema_registry.SchemaRegistryException. GlueAvroDeserializercompiles a JSON schema version with fastjsonschema, as aws-glue-schema-registry 1.1.3 did, and theglueextra needs fastjsonschema 2.22.1 or later, where 1.1.3 accepted any 2.x release from 2.15. A JSON schema version that fastjsonschema cannot compile, or a record that is not JSON, does not match the schema or cannot be checked against it, raisesSerializationError, where 1.1.3 raised the exception of fastjsonschema, orjson or Python, or its ownValidationError.- The
confluentextra also installsjsonschemaandprotobuf, and theglueextra also installsprotobuf. - The
kafkaextra no longer installs kafka-python.KafkaAdminConnectortakes aiokafka's admin settings, so a kafka-python-only setting such asssl_cafileis refused: passssl_context. A kafka-python token provider is refused too: passKafkaAdminConnectorthe aiokafka provider thatKafkaEgresstakes, as AWS MSK (IAM) shows. Code that importskafkaitself installs kafka-python separately.KafkaAdminConnector.create_topicsraises theaiokafka.errorsclass for a broker error, such asaiokafka.errors.InvalidReplicationFactorError, where it raised thekafka.errorsclass: catchaiokafka.errors.KafkaErrorinstead.
Version 0.26.0
- The
confluentextra no longer installs confluent-kafka. desflow writes and reads the Confluent Schema Registry wire format itself with httpx and fastavro, so the extra no longer depends on a package without Python 3.15 support. The bytes on the topic are the same. ConfluentAvroSerializerandConfluentAvroDeserializertake named arguments for a secured registry:auth,verify,cert,timeout,headers,proxyandmax_retries. See Registry Connection.- A Parquet or JSON Lines egress writes to Google Cloud Storage and Azure Blob Storage, from a
filesystemmapping withtype: gcsortype: azure, in YAML, the builder and the low-level API. See Parquet and JSON Lines. - An Iceberg egress writes through a SQL catalog on SQLite or PostgreSQL and through a Glue catalog, and the
icebergextra installs what they need. See Iceberg. - Python 3.15 is supported. On 3.15 the
kafka,confluentandglueextras build aiokafka, and theicebergextra builds pyiceberg and zstandard, from source, which needs a C compiler. See Installation. GlueAvroSerializerserializes a record. It failed on every record in every release since 0.7.0.- The docs are reorganised. Learn holds the tutorial and, after it, the concept pages, each explained from the tutorial's shop. Command Line and SimPy and desflow are sections of their own. The examples are the Runnable Examples group of How-To, and its overview page holds Services for the Examples, the odctl profiles each example needs, which moved from Installation.
- Model code is shown in YAML, Builder and Low-level tabs on every page except those about one way of writing, such as Builder API. Every How-To script has all three forms, each with a test, except on the two pages about blueprints alone, Call Python from YAML and Set Up Your Editor.
- The Cheat Sheet and YAML Format pages are removed. YAML Keys is the one list of blueprint keys, and both old addresses lead to it.
- Each page's address follows the sidebar, such as
cli/run/fordesflow runandhow-to/modelling/breakdowns/for Model Breakdowns. Every old address, and every old link to a moved section, leads to the new place.
Upgrading
pip install "desflow[confluent]"no longer installs confluent-kafka. Code that importsconfluent_kafkaitself installs it separately.- The
confluentandglueextras no longer install dataclasses-avroschema, which desflow's own code does not use. Code that builds its schema withAvroBaseModelinstalls it withpip install "dataclasses-avroschema[pydantic]". - The confluent-kafka keys passed as
**registry_kwargs, such asbasic.auth.user.info, still work with aFutureWarningand are removed in 0.30.0. Useauth=,verify=,cert=,timeout=,proxy=,headers=andmax_retries=instead. OAuth bearer sources other thanSTATIC_TOKENare refused: pass the token inheaders=.retries.wait.ms,retries.max.wait.msandurlare refused: setmax_retries=and pass the URL asregistry_url.cache.capacityandcache.latest.ttl.secare ignored. - Registry and framing errors are
SchemaRegistryErrorandSerializationError, importable fromdesflow.connectors.egress.kafkaordesflow.connectors.ingress.kafka, not confluent-kafka's classes. - The subject is always
<topic>-value. confluent-kafka 2.14.0 and later first asked the registry for a topic association, so on a registry with associations defined it could use another subject. - The deserializer looks up a writer schema by its id alone. confluent-kafka 2.11.0 and later also sent the subject, which makes the registry search every schema context, so a schema registered only in a context other than the default one is no longer found.
- A writer schema with enabled Data Contract read rules (domain or encoding) is refused, including a rule with
onFailure: NONE, which confluent-kafka skipped. - A connection error to the registry is retried up to
max_retriestimes (3 by default), with a random wait of up to 1, 2 and 4 seconds; it failed at once before. - Each step of a request to the registry (connecting, sending, reading) times out after 10 seconds. It never timed out before. Pass
timeout=Nonefor the old behaviour. - The
parquetextra requires pyarrow 16.0.0 or later. It was 14.0.0. Azure Blob Storage needsAzureFileSystem, which pyarrow has from 16.0.0. On Windows, PyPI's pyarrow wheels include it from 24.0.0.
Version 0.25.0
desflow explain FILEshows a model as a graph from its arrivals to its exits, without running it. For each resource it gives the utilisation, the status and, where the queueing formulas apply, the waits. It also names the bottleneck and the capacity that brings utilisation to a target. Where a formula does not apply, it says why.--jsonprints the same result as JSON, which is experimental through 1.x. See Explain the Load.desflow explain --verifyalso runs the model unpaced, once or with--replications N, and compares its statistics with the formulas.context.analyze()gives the same result in Python. See Check Against Theory.desflow distlists the distribution families, shows one family's parameters, rules and syntax, and converts its parameters to a mean and standard deviation and back, such asdesflow dist gamma --mean 1.5 --std 0.4832. See Measured Times.desflow run FILE --sample Nprints the firstNevents as JSON lines, unpaced, with no ingress and no real sinks.desflow run FILE --stdoutruns the model as usual but prints its records as JSON lines instead of sending them to the file's sinks. It prints every record except thesystem.simulation.lag_secondsreadings. In Python,build,SimulationContext.from_yamlandload_contexttakepreview=Preview(sample=N)for--sampleandpreview=Preview()for--stdout, withPreviewfromdesflow.blueprint.build. See Preview the Records.ConsoleEgress(format="json"), andtype: Consolewithconfig: {format: json}in YAML, write each record to stdout as one JSON line, while the log stays on stderr. See Console.load_context(folder, entry), exported fromdesflow, loads a YAML blueprint or a builder Python file from a local folder or from object storage, such ass3://. See Loading a Model.add_arrival(..., spawn="task")andadd_arrival_replay(..., spawn="task")start the named task for each arrival, asspawndoes in YAML, so a builder model needs no@app.arrival_loopfor it. See Start Them from Arrivals.Router.probabilitiesgives the share of finished instances each next task receives, where the routing rules fix it. See Task Flow.- The Reference section gains one CLI page for each command, generated from the command's help.
- The CLI code moves to
desflow.cli.desflow.blueprint.clire-exportsappandddesfor dynamic-des 0.20.0'sddescommand, until the release that removesddes. - The skill for AI assistants gains
references/cli.md, the help of every command, andreferences/api.md, every namedesflowexports. Its study steps usedesflow experimentanddesflow fit. See Use an AI Assistant. desflow runprints a Console sink's records as they are, one line each, as the pages show them. Before, the log's time, level and logger name started the first record of each batch. See Console.- When a blueprint or the builder gives a resource, store or container a
current_capabove itsmax_cap, the model now lowers it tomax_capwith a warning when it is built, as a live update does.desflow explainthen analyses the capacity the run uses. Before, the run lowered it silently, andexplainused the larger value. - The examples, snippets and pages address the odctl services as
127.0.0.1, such as127.0.0.1:9092, notlocalhost. odctl 1.1.0 and later publish every port on127.0.0.1only. On macOSlocalhostresolves to::1first, so the Kafka clients could not connect. See Services for the Examples. - When the installed release's tag is not published yet,
desflow examples getsays so and suggests--ref main, instead of saying the example was added after this release. desflow examples get --dest DIRprints the run command withDIRin its path. It printed the path withoutDIR.desflowon its own lists the commands grouped by task, as odctl does. Its help, and the--helpof each command, end with examples. See Command Line.desflow examples listlists the examples, asdesflow exampleson its own still does.desflow examples getalso prints thedesflow explaincommand of an example whose bottleneck it names. See Example Projects.- The docs gain the tutorial Complete Model, a whole study from observed data to a recommendation. They also gain the How-To page Check Against Theory and the User Guide page Command Line, which uses every command and option on one shop. Every page is rewritten for a first-time reader. Each term of theory is explained in one section, and other pages link to it. The How-To pages are grouped into Modelling, Data and Kafka, and Tools and Workflow. Each page ends with links to the previous and next pages. The tutorials, the User Guide, the How-To pages and the example Quick Starts use each command where its output answers the page's question, such as
desflow explainon Priorities and End Conditions anddesflow run --stdouton Patience. Links to files in the repository open the files of the release. - The sidebar is regrouped. The User Guide has four groups, Writing a Model, Model Parts, Results, and Live Runs and Connectors, then Command Line. The How-To pages are in four groups and are named for their task, such as Model Breakdowns. The User Guide and How-To sections each open on a page that says what the section is for, and the Examples section is named Example Projects. Coming from SimPy moves to Learn, after SimPy and desflow, and gains the two tables that end SimPy and desflow before: what desflow changes, and the form of each SimPy construct in desflow. Every page keeps its address.
Upgrading
desflow run FILE.pyexits 2 withError: desflow run takes a YAML blueprint; run a Python file with python FILE. In 0.24.0 it exited 1 with a YAML parse error. Run a builder file withpython FILE.py.desflow fitandFitResult.text()label the KS columnsKS statandKS p (approx.), where 0.24.0 printedKSandp (approx.). The two columns are 8 and 14 characters wide, not 7 and 11. A script that reads the table by column position reads the new widths.- A tool that read the time and level before a Console record of
desflow runreads the record alone. Every other change is an addition or a fix.
Version 0.24.0
- A JSON Schema of a blueprint ships in the package and on the site. An editor with the Red Hat YAML extension completes keys and marks a misspelt key or a value of the wrong kind as you type. See Set Up Your Editor.
desflow schemaprints the schema.- YAML Keys lists every key of a blueprint with its type and default, generated from the schema.
- A skill for AI coding assistants,
skills/desflow/, teaches them to write, check and run blueprints, and to run a study with a seed, a warm-up and replications. It installs by copying the folder or withgh skill install. See Use an AI Assistant. resource.queue.items, which 0.23.0 deprecated, is removed. See Upgrading.- The Learn section gains A Simulation Study, Checking and Validating, Queueing Basics, Output Analysis, Coming from SimPy, Common Mistakes and Readiness and Reporting, with the tutorials Checking a Model, Live Changes and Streaming Out.
- The How-To section gains Test a Model, Analyse the Event Log, Animate a Run and Share by Weight, which shares one resource between classes by weight.
- Run Length states the order of events due at the same time.
- Why desflow says what the library is for.
- The JSONL file sink's progress line every 10 batches names it
JSONL Writer. It saidParquet Writer.
Upgrading
resource.queue.itemsis removed, as the 0.23.0 Upgrading section announced.resource.queueis the list of waiting requests in service order, and[request for _rank, request in resource.queue.items]becomeslist(resource.queue). Every other change is an addition or a fix.
Version 0.23.0
- A
waitnestsany,allandat_leastto any depth, andat_least: nwithof:is done whennof its branches are, as SimPy'sConditionwith a count rule. An emptyanyorallsucceeds at once. Arequest,getorputcan be a branch, a won request holds its unit through the service, and a losing branch is cancelled. Request branches count in the order of acquisition, and the requests of oneallare made together. See Wait for One or All. - A
timeoutbranch can carry a value that the waiter receives, and arunningbranch waits until the running instances of a task on the waiter's entity, and the entities it spawned, all end. See Signals and Waiting. process.name,process.instanceandprocess.targetin a condition or an expression read the running task, as SimPy's active process, andt, the simulation clock, is now checked against SimPy'senv.now. See Read the Current Time and Processes and the Low-level API.spawnon a task starts a task as a new entity without waiting for it, keyed<task>-nwith the spawning entity's key inattr.parent, andafterstarts it later, as SimPy'sstart_delayed.subscribeinterrupts a task when an event happens, as SimPy'ssubscribe_at. See Signals and Waiting.signals: {closing: {once: true}}(add_signal(name, once=True),Signal(env, name, once=True)) is a signal that triggers once, as SimPy'sEventdoes: a wait after the firing resumes at once and a second firing is refused. See One-Shot Signals.on:on a task or a signal runs actions when it fires or ends: fire a signal with a value, or copy another outcome withfrom, spawn a flow, interrupt a task, or set a value. Afireon a one-shot signal takesurgentandafter, andenv.schedule(event, priority, delay)schedules a low-level event. See Actions When an Event Happens.run.on_unhandled_failure: stopends the run with the error when a task, child flow or one-shot signal fails and nobody handles it, as SimPy does;continue, the default, keeps the run going as before. See Failures.- The
process-eventsexample shows a one-shot signal, a spawn after a delay, a subscribe and anon:action. See Process Events. advance(until)runs a started builder or YAML run to a time or an event and returns with the run open,finish()runs on to the end, andresume_pacing()paces again after a pause. A run in segments writes the same records as one run. See Run in Segments.simulation.environment: !python module.Class(SimulationContext(environment=Class)) runs the model on a subclass ofDynamicRealtimeEnvironment, so an override ofsteporschedulesees its events. See Run on Your Own Environment.simulation.strict: truein YAML andSimulationContext(strict=True)in the builder raiseRuntimeErrorwhen a paced run falls more thanfactorseconds behind, as the low-levelstrictdoes; both default to False. See Strict Pacing.sync()on the environment resets the wall-clock anchor, as SimPy's does, sostrictstops counting the time before it. See Strict Pacing.- A resource may leave its capacity out:
current_capis then 1 andmax_capiscurrent_cap, soresources: {packers: {}}andadd_resource("packers")declare one unit, and aDynamicResourcewith no registry entry registers itself with one unit, assimpy.Resource(env)has.current_cap: .inf(float("inf")) is a resource with no limit; it publishescapacityasnulland autilizationof 0. A store or container may leave its capacity out too, and then has no limit, assimpy.Store(env)andsimpy.Container(env)have:stores: {parcels: {}},add_store("parcels"),DynamicStore(env, "Shop", "parcels"). Amax_capalone gives thecurrent_cap, andcurrent_cap: .infalso means no limit. A store or container with no limit publishescapacityasnull. Before, leaving a capacity out was refused in every API, and a store with an infinite capacity failed in all three. See Resources and Stores and Containers. users.<resource>andqueue.<resource>in a condition read how many units are held and how many requests wait, andget_resource(name).count,.usersand.queueread them in code. A granted request hasusage_since, and a release is an event,DynamicRelease, that holds its request. See Resources.queue_limiton a resource refuses a request made while that many wait, as SimPy'sSortedQueue(maxlen): a task balks and publishesbalkedwith the resource, arequestbranch of awaitfails the wait, and the low-levelrequest()raisesQueueFull, aRuntimeError. See Queue Limit.ResourceBaseis the classDynamicResourceis built on, with SimPy's hooks: subclass it, override_do_putor_do_get, setPutQueueor therequestandreleaseclasses, and name the class withtype: !python module.Classoradd_resource(..., type=Class). See Custom Resource Types.StoreBaseandContainerBaseare the classesDynamicStoreandDynamicContainerare built on, with SimPy's hooks: subclass one, override_do_putor_do_get, setPutQueueorGetQueue, or bindputandgetto event classes of your own, and name the class withtype: !python module.Classoradd_store(..., type=Class)andadd_container(..., type=Class). See Stores and Containers.putters.<name>andgetters.<name>in a condition read how many puts and gets wait at a store or container, andcount(<store>),items(<store>),inandnot inread what a store holds. A condition that reads one of them is checked again when a put or get joins or leaves a queue. See Stores and Containers.wait: falseon a task'sputorget("wait": Falsein the builder) makes the request and goes on without waiting for it, as a SimPyputthat is not yielded. See Stores and Containers.- A store priority in YAML is kept as given, so a list, a mapping, null or a date is accepted, as in the builder. Before, only a number or an expression was, and the others were refused at load. The priorities in one store must still be orderable against each other. See Stores and Containers.
- The
fuel-dockexample shows a store type of your own, a put that is not waited for and a wait onputters.. See Fuel Dock. - A YAML experiment file whose check sets
on: meanoron: interval, the documented key, now loads. In 0.22.0 YAML read the key asTrueand the load failed with "Keys should be strings". - A task that balks with
balk:now gives back the units it kept from the task before it withkeep; in 0.22.0 such a unit was held for ever. - Raising a store's capacity to a finite value granted only one waiting put; it now grants every put that fits, as raising a resource's capacity grants every request. A rise to no limit already did, and 0.22.0 has the defect.
- The User Guide pages Resources, Tasks and Processes, Time and Pacing, and Experiments are split into one page per topic. A link to a section that moved, such as
user-guide/resources/#breakdowns, opens the page that holds it now. - Every SimPy 4.1.2 feature on the SimPy Compatibility page is now native in all three APIs or a difference kept by design, so the page has no Not Yet section. A later SimPy release's features are made native in the desflow release that moves to it.
- A run that ends at a statistics interval time published every value twice at that time in 0.22.0. It now publishes each value once at that time, plus the values that changed after the interval's reading, so the last value of each statistic is the state at the end. This applies when the run ended there on a
stopcondition, on an event given torun(until=), at the end of a drain, or withclose()afterstep()had reached that time.
Upgrading
- A task's SimPy process is named after the task, not
run. The process of a task in a root flow ends with the instance's finished payload as its value, also when it hands on to the next task; it wasNone. It is stillNonewhen the instance balks, reneges or is abandoned, and when it fails underon_unhandled_failure: continue; understopa root task that fails with noon_failraisesTaskFailed. A child flow's process still ends with the flow's last payload. - Tasks without a
priorityon aqueue: priorityresource, andDynamicResource.request()called without a priority, rank 0, not 1, as SimPy's default. A task with no priority now goes ahead of one with priority 1. - A
preemptedevent carriespreempted_by, the key of the entity that took the unit, andusage_since, the time the preempted task was granted it. Storage sinks gain two columns. resource.queueis a list of the waiting requests in service order, whereresource.queue.itemsheld(rank, request)pairs.queue.itemsstill works with aFutureWarningand is removed in 0.24.0.- A store priority keeps the value it is given: a priority whose expression gives a numeric string, such as
attr.priorityset to'10', now compares as text (a literal10or'10'in a blueprint is still a number), and priorities of mixed types raiseTypeErrorat the put instead ofValueError. ATruepriority, which the builder refused and YAML cast to1.0, is kept asTrue, with the same service order. putters_waitingandgetters_waitingonDynamicStoreandDynamicContainer, andqueueonDynamicResource, no longer count a put, get or request that is being granted, which SimPy still holds in its queue during the grant. Awatch_changescallback called during a grant read 1 in 0.22.0 and reads 0, also for a request granted at once.add_resourceandadd_storerefuse a fractionalcurrent_capormax_cap, as YAML does; 0.22.0 rounded it down.- The low-level
strictdefault is True, as SimPy's: a paced run more thanfactorseconds behind raisesRuntimeError. Passstrict=Falsefor live runs with sinks, as the low-level examples now do. The builder and YAML (simulation.strict) default to False.strictis never checked atfactor0, including beforego_live_at. See Strict Pacing. step()andpeek()count only the model's events, not desflow's statistics, flush, lag, ingress and watcher processes:peek()skips them andstep()runs the ones due before the model's event in the same call, so a script steps a model as it would in SimPy.run()still processes every event. A script that counted statistics readings as steps counts fewer. See Run Length.run()andfinish()return the value of the event that stopped the run, as SimPy'senv.run(until=event)does; they returnedNone. Astopon a signal that fails now raisesSignalFailedafter the run closes, where a re-arming signal's failure used to end the run with no error;desflow runexits with an error. See Run Length.
Version 0.22.0
The low-level API is SimPy, with desflow's own resources, stores and containers, so SimPy code runs there. The SimPy Compatibility page lists every modelling feature of SimPy 4.1.2 with its status in each API: 48 are native in YAML, the builder and the low-level API and checked against plain SimPy, 48 are not yet, which 0.23.0 makes native, as the Roadmap says, and 2 are differences kept by design.
- A SimPy Compatibility page lists every SimPy 4.1.2 modelling feature with its status in each API, its forms and the tests that compare it with SimPy.
- Patience and balking: a task's
patiencetakes a duration such as30 s, and orders that leave or refuse to join are counted astasks.<task>.renegedand.balked. See Patience and Reneging and Balking. - A store can start with items:
stores.<name>.items,add_store(..., items=[...])andDynamicStore(..., items=[...]); a priority store takes[priority, item]pairs.initial_timetakes a duration such as8 h. See Stores and Containers and Run Length. - A store
getcan take afilter, such asitem.area == attr.area, andpatiencecovers the wait for agetas well as for a resource;item_fieldadds the item a YAML get took to the payload. See Stores and Containers and Filtered Inventory. - Tasks interact as SimPy processes do:
signalsthat tasks fire and wait for, awaitfor one or all of signals, child flows, timeouts and conditions, andfail,fail_reasonandon_failfor a task that fails.context.spawnreturns the process it starts, andcontext.callstarts a child flow. See Process Interaction, Signals and Waiting and Wait for One or All. - Interrupts and preemption: an
interrupton a task or scenario step, orcontext.interrupt, interrupts running task instances;on_interrupt: resume,restartorabandonsays what each does;preempt: trueon a priority resource lets a lower priority value take a busy unit. See Interruptions and Priority and Preemption. - A task can hold several resources,
resource: [picker, bench], acquired in that order, and hand a unit to the next task withkeep; a model that acquires resources in opposite orders is refused. See Competing Resources and Several Resources. - End conditions:
run.stopends a run at a signal or a condition, andrun.drain(ordesflow run --drain) stops the arrivals atuntiland finishes the entities already in the model;start(),step(),peek()andclose()step a builder or YAML run one event at a time, andfinish()runs a started run on to its end. The statistics countflows.<arrival>.startedandin_system, andtasks.<task>.in_progress. See End Conditions and Run Length. - A low-level request can be cancelled with
request.cancel()and given back withresource.release(request), as in SimPy. See Resources. - Breakdowns:
resources.<name>.breakdown,add_breakdownandrun_breakdownsmake a resource fail at random and be repaired, interrupting the work it was doing or letting it finish; each failure and repair publishesdownandrepaired, and the statistics countresources.<name>.down,downtimeandfailures. The Preemptive Breakdowns guide becomes Breakdowns. See also Priority and Preemption. - Schedules in every API: the builder's
add_scenarioandadd_step, and the low-levelrun_scenario, take the same steps as a blueprint'sscenario. See Time-varying Arrivals and Changing Demand. - Replay: an arrival's
replay,add_arrival_replayand the low-levelread_tracetake arrivals from the rows of a CSV or Parquet file, or a list of dicts, at their recorded times, with the row's fields as attributes. See Local Replay and Arrivals and Services. - Faster runs at
factor: 0: SimPy's own step processes each event, a record's timestamp is formatted once per second of the clock, andConsoleEgresslogs a batch with one call. A low-level workload runs 1.14 times faster than in 0.21.0, and printing every record costs about a seventh of what it did. See Performance Tuning, whose measurementsbenchmarks/throughput.pyrepeats. simulation.lag_intervalandSimulationContext(lag_interval=)set how oftensystem.simulation.lag_secondsis published. See Clocks and Pacing.- The record contract: Records and Telemetry gives every field its type, and a docs test fails when the page and the payload models differ.
simulation.timezone,SimulationContext(timezone=)andDynamicRealtimeEnvironment(timezone=)name the zone timestamps are written in, and Statistics defines which entities each count covers. - Replications:
desflow run FILE --replications Nanddesflow.replicaterun a model with seeds spawned from one seed, ending each replication atuntil, astopor the end of adrain, and give each statistic's mean and 95% half-width; the model's sinks receive the result once, as<sim_id>.stats.<measure>.replication.<i>,.mean,.half_widthand.n. See Replications and Trusting Results. - Experiments:
desflow experimentanddesflow.run_experimentcompare designs with paired replications and sweeps, ending each run atuntil, astopor the end of adrain, apply checks that fail the command, compare with observed values, and write CSV and JSON results and areport.md;desflow reportbuilds the report again. See Experiments, Comparing Designs and Parameter Sweeps. - Input fitting:
desflow fitanddesflow.fit_columnfit a column of measured times to every distribution family, with diagnostics and a plot, and write settings a blueprint loads with the new!includetag. See Input Fitting, Fit the Inputs and Fit a Distribution. - An unknown key in a blueprint or an experiment file names the closest key that place takes, such as "did you mean 'patience'?", and a misspelt name of a resource, task, signal or path names the closest one; the errors this release adds say how to fix them.
- Easier to start: the Home page has a "Where to Start" table, the Cheat Sheet gives the common YAML keys and builder calls side by side, and every tutorial and How-To page gives its level: Basics, Intermediate or Advanced.
Upgrading
- desflow needs SimPy 4.1.2 or later, where it accepted 4.0.0, and pydantic 2.6.0 or later, where it accepted 2.0.0, with which desflow 0.22.0 does not import. The conformance tests check desflow against SimPy 4.1.2, which chooses the user to preempt differently from 4.1.1 on equal keys; SimPy 4.0 also imports
pkg_resources, which a new Python environment does not have. - A store or container built with a capacity of 0 now works, as a resource does, and holds nothing until its capacity rises; SimPy's
ValueErrorstopped it before. - A
priorityon a storegetis refused; priority orders aputinto a priority store. - Resources grant units with the same events as SimPy's
Resource, so requests at the same simulation time are granted in SimPy's order, which can change seeded output where such ties occur. patiencenow covers a task's whole wait, itswait, its resource and itsget, where it covered only the resource, so a task can renege while it waits for stock. It counts waiting time only, so a preempted task that queues again goes on with the time it has left. A task withpatienceneeds a resource, agetor await.- A store without priority is now a SimPy
FilterStore, so agetcan take a filter. While no waitinggethas a filter, it grants gets assimpy.Storedoes, one waitinggeteach time it is triggered, as before; while a filteredgetwaits, it looks at every waitinggeteach time, asFilterStoredoes, so gets at the same instant can be granted in another order. - A builder task whose function raises, or a YAML task whose
!pythonpayload function raises, now fails the task, publishingfailedwith the error as its reason and ending the entity's flow, instead of stopping the run. DynamicResource.poolandDynamicResourceRequest.cancelledare gone: the resource grants units as SimPy'sResourcedoes. Callrequest.cancel()to leave the queue;resource.queue.itemsholds(rank, request)pairs, where it heldPriorityItems. A request that gives up, bycancel()or by reneging, now leaves the queue at once, solen(resource.queue.items)and thequeue_lengthandqueue_length_avgstatistics no longer count it.- The built-in statistics publish new names:
tasks.<task>.in_progressfromminimal, andflows.<arrival>.startedandin_systemfrombasic. A low-level process that callsenv.stats.task_queued,task_startedandtask_finishedpublishesin_progresstoo, and one that callsflow_startedpublishes the flow counts. - Events with the statuses
balked,reneged,failed,interrupted,preemptedandresumed, and a resource'sdownandrepairedevents, keyed by its name, reach the sinks, so a consumer that filters onstatusshould expect them. - A scenario step now applies before every other event at its time, as SimPy applies its own
until, so an arrival at the step's time draws its next gap from the new value; seeded runs whose steps fall on the time of another event can give other numbers. - At
factor: 0,system.simulation.lag_secondsis no longer published unlesslag_interval(low-levellag_monitor_interval) is set; a run that goes live publishes it from the go-live instant. At afactorabove 0 it is published every simulation second, as before. ConsoleEgresslogs each batch with one call, one line per record, so the logging format's prefix starts each batch rather than each line. Code that reads the console log record by record splits each message into lines.- At
factor: 0, with nogo_live_atstill to come, each event is processed by SimPy's own step andstrict=Trueis not checked, as there is no wall clock to fall behind; while ago_live_atis pending, the real-time step runs as before. - Every
timestampnow carries a time zone offset, such as2026-10-03T12:00:00.000+10:00, so a consumer that parses it must accept+HH:MM, and a sink column without a zone, such as a PostgreSQLtimestamp, now gets the UTC wall time. By default timestamps are in UTC,+00:00, where they were in the machine's local time with no offset; settimezoneto an IANA name to write them in a zone. Alogical_start_timeorgo_live_atwithout an offset,nowand the default start are read intimezone, else in the zone oflogical_start_time, else in UTC, not in local time: for a start or go-live of now, passdatetime.now(timezone.utc), notdatetime.now(), whose local wall time would be read in that zone and be off by the difference.env.start_datetimealways has a time zone. A start and a go-live, one with an offset and one without, no longer fail. The clock runs in UTC, so a daylight-saving change no longer adds or drops simulation time. - A Parquet file now has a column for every key of any row in its batch, where the first row's keys alone were written and later keys were lost; a key that first appears in a later batch adds a column to the later files, and an integer column that later gets a float becomes a double. An Iceberg table not named in
schemasgains such a key as an optional column. A batch a Parquet or Iceberg sink cannot write is logged at ERROR, where it stopped the sink with no error. - A container's starting level keeps its type in YAML, the builder and the low-level API, so
level: 1,level=1orinit=1gives a level of 1, as SimPy does, where it gave 1.0. - A payload key that starts and ends with two underscores, such as
__table__, is reserved: Kafka, Parquet, JSON Lines, Iceberg and the console drop it, with a warning, where they wrote it as data. Rename such a key, such as__source__tosource, to keep it. PostgreSQL and Redis still route by__table__and__stream__. Routers, such as a Kafkatopic_routeror a storagepath_router, no longer see the dropped keys; PostgreSQL and Redis still write any other such key as data. - Fitting needs the new
fitextra, which installs scipy and matplotlib:pip install "desflow[fit]". Theallextra includes it, sodesflow[all]now installs both. - The
glueextra needs boto3 1.26.0 and aws-glue-schema-registry 1.0.0 or later, and theglueandconfluentextras need dataclasses-avroschema 0.65.7 or later; an environment with older releases upgrades them.
Version 0.21.0
The simulation model grows: more distributions, stores and containers that tasks use, flows of tasks, built-in statistics and ground-truth labels. The docs gain a Learn section with a tutorial, the Shop, and a User Guide with one page per part of the library.
- Five new distributions,
uniform,triangular,gamma,weibullandempirical, and, in YAML, durations such as5 minfor every time-valued parameter. The builder's distribution parameters take numbers in seconds, and itswarmupandrun(until=)also take durations; the low-level API takes numbers in seconds. See Arrivals and Services and Distributions. - Each distribution draws from a random stream of its own, so adding an arrival leaves the other draws unchanged. A run without a seed draws one and logs it, and
desflow run --seed Nrepeats it. See Randomness and Seeds. - Stores and containers that tasks take from and add to, with
getandput; a container'slevelis a live path. See Stores and Containers. - A queue order per resource,
fifo,lifoorpriority, and apriorityper task. See Resources. - Flows of tasks:
nextby name, condition or weights, entity attributes withattributesandset, and timed steps with a service and no resource. A builder arrival loop takes a task's instance id fromcontext.next_instance(task), the counternextuses too. See Task Flow. - Built-in statistics at five levels,
stats: nonetostats: all, and awarmupthat leaves the start of a run out. See Statistics. - Ground-truth labels: a scenario step's
recordpublishes what it changed towhen: labelssinks only. See Live Parameters. - Every ingress connector reads
pathandvalue, and a bad message is logged and skipped. See Ingress.
Upgrading
- Python 3.11 or later is needed.
- Ingress messages use
pathandvalue: Kafka'spath_idand Redis'sparam_pathandparam_valueare renamed, and a Kafka message, or a PostgreSQL ingress row, with a nullvalueis now refused. Callsend_config(topic, path=..., value=...)where it was called withpath_id=. - For each table the PostgreSQL ingress reads,
simulation_paramsby default, rename the columns:ALTER TABLE simulation_params RENAME COLUMN param_path TO path;andALTER TABLE simulation_params RENAME COLUMN param_value TO value; - An event's key is now the entity's key, the arrival name and a counter from 1 (
orders-1), nottask-<task id>, and every event has two new fields afterkey:taskandinstance. PostgreSQL rows gaintaskandinstancecolumns where the table has them. See Records and Telemetry. telemetryentries in a blueprint no longer takepublish. Usestats: minimal, which publishes the same readings asShop.stats.resources.<name>.<reading>, andfunctionfor readings of your own.- Seeded runs give other numbers than before, because each distribution has its own random stream, and requests of equal priority are now served first in, first out.
- A distribution parameter the family does not take, such as
rateon anormal, is now refused at build. The builder's distribution parameters are keyword-only: writeadd_service("packing", "normal", mean=3). - Under the default
fifoqueue a request'spriorityis ignored. Add the resource withqueue="priority"(YAMLqueue: priority; low-levelDynamicResource(env, sim_id, name, queue="priority")) to rank requests; a taskpriorityon another order is refused. context.wait_for_arrivalnow yields the newEntity. Pass it to the task asentity=, or the task starts an entity of its own keyed by the task's name.- A container's
current_capis its capacity, its largest amount; a model that used it as a level moves tolevel.DynamicContainer(init=None)now starts at the registry's level, 0 unless theContainerConfiggives one.
Version 0.20.0
The package dynamic-des is renamed desflow. The classes, the YAML format, the records and the extras are the same as in 0.19.0.
Upgrading
- Install
desflowinstead ofdynamic-des, with the same extras:pip install desflow,uv add desflow, or for examplepip install "desflow[kafka]". The last dynamic-des release, 0.20.0, installs desflow with the same extras. To install desflow beside dynamic-des 0.19.0 instead, uninstall dynamic-des first: with pip, uninstalling it afterwards also removesdynamic_desandddes, andpip install --force-reinstall --no-deps desflowputs them back. - Import
desflowinstead ofdynamic_des, for examplefrom desflow import SimulationContext. - Run the
desflowcommand instead ofddes, for exampledesflow run shop.yamlordesflow examples. import dynamic_desstill works in this release. It shows aFutureWarningand gives the desflow modules, submodules such asdynamic_des.connectors.egress.kafkaincluded. Theddescommand still works too: it prints a warning and runsdesflow. Both are removed in 0.30.0.- The docs are at jaehyeon.me/desflow, and the repository is jaehyeon-kim/desflow on GitHub.