A lightweight markup language for structured content with powerful section-based organization, metadata filtering, and validation capabilities.
Project description
FlexTag: Tag-Based Data Organization
Alpha Status: Under active development - Report Issues | Send Feedback.
Hashtags Meet Data Integrity
FlexTag: Organize with #hashtags, validate with schemas, search like a search engine. One syntax from a single config to an entire directory.
Why developers choose it:
- Data containerization - Tag and organize data across sections, files and directories. Self-contained and composable.
- Metadata-based organization - Tags and parameters everywhere. Search files and sections uniformly.
- Flexible filtering - Select files and sections by tags and parameters, query as one dataset.
- Format agnostic - Mix FTML, JSON, YAML, text, binary. Search uniformly.
- Schema validation - Data integrity through metadata-based matching.
- Don't repeat yourself - One syntax mirrors across data, schemas, and queries.
- Plain text always - Human-readable, git-friendly, AI-friendly.
Quick Example: Tag, Store, Search
See how FlexTag organizes data with hashtags:
[[#database #production]]: ftml
host = "prod-db.example.com"
port = 5432
max_connections = 100
[[/]]
[[#database #development]]: ftml
host = "localhost"
port = 5432
max_connections = 5
[[/]]
[[#cache #production]]: ftml
host = "redis.example.com"
ttl = 3600
[[/]]
Key Insight: Tags on sections become your search queries. No separate query language to learn.
Your First Python Search
Now let's search this data using the tags:
pip install flextag
import flextag
# Load the file
view = flextag.load(path="config.ft")
# Search using the same hashtags
production = view.filter("#production") # database + cache sections
databases = view.filter("#database") # production + development
prod_db = view.filter("#database #production") # exact match
print(prod_db[0].data) # {'host': 'prod-db.example.com', 'port': 5432, ...}
The tags you write ARE your search queries. That's the whole idea.
Filtering is case-insensitive — tags can be written in whatever case makes sense (#MyTag, #NASDAQ, #my_tag) and queries will match regardless of case.
Tagged Parameters: Structure Without Repetition
Use tagged parameters (#-prefixed values) for structured, searchable metadata:
[[#stock exchange=#nyse symbol=#aapl sector=#tech]]: ftml
name = "Apple Inc."
market_cap = 3200000000000
[[/]]
[[#stock exchange=#nyse symbol=#goog sector=#tech]]: ftml
name = "Alphabet Inc."
market_cap = 2100000000000
[[/]]
[[#stock exchange=#nasdaq symbol=#msft sector=#tech]]: ftml
name = "Microsoft Corp."
market_cap = 3100000000000
[[/]]
Search any way you want:
view.filter("#stock") # all stocks
view.filter("#nyse") # tag search — finds #nyse in any parameter
view.filter("exchange=#nyse") # key-value — NYSE stocks specifically
view.filter("#stock #tech") # AND — tech stocks
view.filter("market_cap>2500000000000") # comparison — large caps
# Discover what values exist (cascading dropdowns, autocomplete)
view.filter("#stock").values("exchange") # → ["#nyse", "#nasdaq"]
view.filter("#stock").values("sector") # → ["#tech"]
view.filter("#stock").tags() # → ["#stock", "#nyse", "#nasdaq", "#aapl", ...]
Key-exists check — find sections that have a parameter regardless of value:
view.filter("exchange=") # any section with an exchange parameter
Tagged parameter values prefixed with # are searchable as tags. Plain string values (like name="Apple Inc.") are not — they're queried by key-value match only.
Tag Paths: Hierarchical Tags
Link tags into paths with # to create searchable hierarchies. Every segment is independently searchable — no need to remember the full path. A section can belong to multiple paths, like a product shelved in multiple departments:
[[#dept#household#cleaning #dept#hardware#cleaning]]: ftml
name = "Push Broom"
price = 24.99
[[/]]
[[#dept#household#cleaning]]: ftml
name = "Sponge Pack"
price = 4.99
[[/]]
[[#dept#hardware#tools]]: ftml
name = "Claw Hammer"
price = 12.99
[[/]]
Every segment is a search target:
view.filter("#cleaning") # push broom + sponge pack (any path)
view.filter("#hardware") # push broom + claw hammer
view.filter("#dept#household") # push broom + sponge pack
view.filter("#household#cleaning") # push broom + sponge pack
Trailing # — direct children only:
view.filter("#dept#") # → household, hardware (one level deep)
view.filter("#dept#hardware#") # → cleaning, tools (not deeper)
Explore the hierarchy with .children() and .parents():
view.children("#dept") # → ["household", "hardware"]
view.children("#dept#hardware") # → ["cleaning", "tools"]
view.children("#dept", depth=0) # → {"household": {"cleaning": {}}, "hardware": {"cleaning": {}, "tools": {}}}
view.parents("#cleaning") # → ["household", "hardware"]
Space between tags means separate, independent tags. No space means a path:
[[#dept#hardware#tools #brand#stanley]]
#dept#hardware#tools— one path (three segments)#brand#stanley— another path (two segments).filter("#hardware")matches,.filter("#stanley")matches,.filter("#hardware #stanley")matches (AND)
Data Integrity: Schema Validation
FlexTag validates data using the same tag-matching system. Schemas are sections too:
// Schema validates any section with #product tag
[[#product]]: ftml-schema
name: str
price: float<min=0.01>
[[/]]
// Data automatically validated against matching schema
[[#product #electronics name="Keyboard"]]: ftml
price = 79.99
description = "Mechanical keyboard"
[[/]]
[[#product #clothing name="T-Shirt"]]: ftml
price = 19.99
size = "M"
[[/]]
# Validation happens automatically on load
view = flextag.load(path="products.ft", validate=True)
Key Points:
- Schema uses
:for type declarations, data uses=for values - Schema
#productmatches all sections with the#producttag - Extra fields like
descriptionandsizeare allowed - schemas only enforce what they declare - Constraints work just like FTML:
price: float<min=0.01, max=99999.99>
Schema Layering
Multiple schemas can apply to the same section through tag matching:
// Base schema for all products
[[#product]]: ftml-schema
name: str
price: float
[[/]]
// Additional requirements for electronics
[[#product #electronics]]: ftml-schema
warranty: str
sku: str
[[/]]
A section with #product #clothing validates against the base schema (name, price).
A section with #product #electronics validates against both (name, price, warranty, sku).
No inheritance configuration needed - it's just tag matching.
Python Type Hint Foundation
The core syntax comes from Python type hints, used consistently everywhere:
name: type = value
Section Parameters (compact, no spaces):
[[#config version:int=2 debug:bool=false]]: ftml
FTML Data Content (readable spacing):
version: int = 2
debug: bool = false
FTML Schema (types without values):
version: int
debug: bool
Same pattern at every level. Only spacing and assignment context change.
Mixed Content in One File
Each section declares its content type - mix any formats freely:
[[#config]]: ftml
host = "localhost"
port = 5432
[[/]]
[[#endpoints]]: json
{"users": "/api/users", "products": "/api/products"}
[[/]]
[[#deploy]]: yaml
provider: aws
regions:
- us-east-1
[[/]]
[[#notes]]: text
Remember to update deploy script before release.
[[/]]
Tags, parameters, filtering, and schemas work identically regardless of content type.
Content Types
Built-in types that FlexTag parses: text, ftml, json, yaml, toml, binary, ftml-schema, schema.
Custom types — put whatever you want. FlexTag stores the type string as metadata and hands the content back as text:
[[#compute]]: python
def calculate(bars, period=20):
return sum(bars[-period:]) / period
[[/]]
[[#style]]: css
body { color: red; }
[[/]]
[[#template]]: html
<div>{{content}}</div>
[[/]]
No warning, no error. FlexTag doesn't try to parse custom types — it just stores them. Your application decides what to do with the content.
Default type is text. No type specified = text:
[[#notes]]
Just some notes.
[[/]]
Filtering by Type
Use :type to filter sections by content type:
view.filter(":ftml") # all FTML sections
view.filter(":python") # all Python sections
view.filter("#config :yaml") # YAML configs specifically
view.filter(":ftml-schema") # all schema definitions
view.filter("!:text") # everything except text
view.filter(":python | :css") # Python or CSS sections
File-Level Metadata
Files themselves can be tagged and searched using the same syntax:
// Tag the entire FILE
[[#plugin #indicator version:str="2.0" author:str="team"]]: file-metadata
[[/]]
// Regular sections inside
[[#config]]: ftml
period = 20
color = "#2196F3"
[[/]]
[[#compute]]: python
def calculate(bars, period=20):
return sum(bars[-period:]) / period
[[/]]
File Filtering Uses the Same Tag Syntax
# Filter FILES by their metadata tags
view = flextag.load(dir="plugins/", filter_query="#indicator")
# Then filter SECTIONS within matched files
configs = view.filter("#config")
backends = view.filter("#compute")
Same hashtags. Same search logic. Section filtering and file filtering use identical syntax.
Indexing External Files
FlexTag can catalog external files without storing their content:
[[#doc #report name="Q4 Sales" date="2025-12-01"]]: ftml
path = "/reports/q4-sales-2025.pdf"
author = "finance-team"
status = "final"
[[/]]
[[#doc #spec name="API v3" date="2025-11-15"]]: ftml
path = "/specs/api-v3.md"
owner = "backend-team"
status = "draft"
[[/]]
drafts = view.filter("#doc status=draft")
reports = view.filter("#doc #report")
Organize any existing files - PDFs, images, CSVs, whatever - through tagging and metadata, without moving or converting anything.
Advanced Features
FlexTag provides powerful capabilities for managing complex data:
- Tag Paths - Hierarchical
#dept#hardware#toolstags where every segment is searchable - Tagged Parameters -
#-prefixed parameter values are searchable as tags - Schema Layering - Multiple schemas apply through tag matching
- Mixed Content - Any format (FTML, JSON, YAML, text, binary) in one file
- File Metadata - Tag and filter entire files like sections
- Parameter Matching - Filter by tag AND parameter values
- Value Discovery -
.values(key),.tags(),.children(tag),.parents(tag)for autocomplete and cascading dropdowns - Plain Text Storage - Git-friendly, human-readable, no special tools
Installation
pip install flextag
Both .flextag and .ft file extensions are recognized.
⚠️ Alpha Status
FlexTag is in alpha (v0.4.0a1). Syntax may change before v1.0. Not recommended for production use.
Contributing
Contributions welcome! Submit a Pull Request or open an issue.
License
MIT License - see LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flextag-0.4.0.tar.gz.
File metadata
- Download URL: flextag-0.4.0.tar.gz
- Upload date:
- Size: 30.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.12.3 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bd713297685d28a3d6cadc310f011ade84c1abad2d6ddb384f618ea8ee3bcda
|
|
| MD5 |
d04d40392a59b0abac184fedd06a9e8e
|
|
| BLAKE2b-256 |
4a259e3d342adeb0fad92898c658cea64e57cbcd7beb7ddb29f8002a976aa811
|
File details
Details for the file flextag-0.4.0-py3-none-any.whl.
File metadata
- Download URL: flextag-0.4.0-py3-none-any.whl
- Upload date:
- Size: 27.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.12.3 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
754408d0117e8c04497f636e7b68516ff22ffd4b4e05bf3575f480df7f8ab84d
|
|
| MD5 |
57c452b277da367a9584c4eee791bfc2
|
|
| BLAKE2b-256 |
72cb9d1140817de1138598a49f8cffebc2aac9fb539b5ae356ca61c6036e84d1
|