Skip to main content

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 #product matches all sections with the #product tag
  • Extra fields like description and size are 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#tools tags 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flextag-0.4.0.tar.gz (30.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flextag-0.4.0-py3-none-any.whl (27.3 kB view details)

Uploaded Python 3

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

Hashes for flextag-0.4.0.tar.gz
Algorithm Hash digest
SHA256 0bd713297685d28a3d6cadc310f011ade84c1abad2d6ddb384f618ea8ee3bcda
MD5 d04d40392a59b0abac184fedd06a9e8e
BLAKE2b-256 4a259e3d342adeb0fad92898c658cea64e57cbcd7beb7ddb29f8002a976aa811

See more details on using hashes here.

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

Hashes for flextag-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 754408d0117e8c04497f636e7b68516ff22ffd4b4e05bf3575f480df7f8ab84d
MD5 57c452b277da367a9584c4eee791bfc2
BLAKE2b-256 72cb9d1140817de1138598a49f8cffebc2aac9fb539b5ae356ca61c6036e84d1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page